[Feat] (MG_Impl, MG_Backend): GL_ARB_timer_query on both backends

Implements GL timer queries end to end: a frontend query registry
(modeled on the sync module - mutex-guarded objects wrapping opaque
backend handles behind optional function pointers) serving
glGenQueries/glBeginQuery/glEndQuery(GL_TIME_ELAPSED)/glQueryCounter
(GL_TIMESTAMP)/glGetQueryObject*/glGetQueryiv with GL 3.3 error
semantics and a graceful zero-result fallback when a backend cannot
time.

DirectGLES backs spans with GL_EXT_disjoint_timer_query (context-
generation-stamped handles, bounded result waits). DirectVulkan gets a
VkTimerQueryManager: per-frame-in-flight timestamp query pools reset at
command-buffer begin (outside render passes), records harvested by
frame serial before their pool recycles, elapsed = masked tick delta x
timestampPeriod; handles are stamped with a renderer generation that
also now guards fence syncs across renderer recreation. GL_QUERY_
COUNTER_BITS reports 0 unless the live backend can actually time
(dynamic IsTimerQuerySupported hook), and a failed blocking read keeps
the handle alive so the real value stays reachable once the frame
submits.

GL_ARB_timer_query is advertised only when the device supports timing
and MOBILEGL_DISABLE_TIMERQUERY is unset - LWJGL keys Minecraft's F3
'GPU: x%' line off exactly that extension string; verified on device
(Adreno 830) on both backends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 01:08:38 +00:00
co-authored by Claude Fable 5
parent 7d31a6fcd7
commit 7eb3994b02
25 changed files with 1917 additions and 91 deletions
@@ -8,21 +8,21 @@
#include "Loader.h"
#include "MG_Util/Types.h"
#include <Config.h>
#if defined(MOBILEGL_IOS)
#include <dlfcn.h>
#endif
namespace MobileGL::MG_Util::BackendLoader {
static Bool UseRetraceAngle() {
const char* value = std::getenv("MOBILEGL_RETRACE_USE_ANGLE");
return value != nullptr && std::strcmp(value, "1") == 0;
return MG_Config::Features.RetraceUseAngle;
}
static Vector<String> AngleLibNames(const char* name) {
const char* angleDir = std::getenv("MOBILEGL_RETRACE_ANGLE_DIR");
if (angleDir != nullptr && angleDir[0] != '\0') {
const String& angleDir = MG_Config::Features.RetraceAngleDir;
if (!angleDir.empty()) {
String path = angleDir;
if (!path.empty() && path.back() != '/') {
if (path.back() != '/') {
path += "/";
}
path += name;
@@ -450,6 +450,8 @@ namespace MobileGL::MG_Util::BackendLoader {
INIT_GLES_FUNC(glBufferStorageEXT)
INIT_GLES_FUNC(glGetQueryObjectivEXT)
INIT_GLES_FUNC(glGetQueryObjecti64vEXT)
INIT_GLES_FUNC(glQueryCounterEXT)
INIT_GLES_FUNC(glGetQueryObjectui64vEXT)
INIT_GLES_FUNC(glBindFragDataLocationEXT)
INIT_GLES_FUNC(glMapBufferOES)
INIT_GLES_FUNC(glMultiDrawArraysIndirectEXT)
@@ -726,6 +728,9 @@ namespace MobileGL::MG_Util::BackendLoader {
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
caps.SupportsBaseInstance = true;
}
if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) {
caps.SupportsDisjointTimerQuery = true;
}
}
}
@@ -911,6 +916,18 @@ namespace MobileGL::MG_Util::BackendLoader {
MGLOG_I(" Indirect draw gl_InstanceID includes baseInstance: %s",
caps.IndirectDrawInstanceIdIncludesBaseInstance ? "true" : "false");
caps.IsAngleRenderer = caps.GLESRendererString.find("ANGLE") != String::npos;
caps.IsAngleLlvmpipeRenderer =
caps.IsAngleRenderer && caps.GLESRendererString.find("llvmpipe") != String::npos;
caps.AvoidSamplerMipmapMinFilter =
caps.IsAngleLlvmpipeRenderer && MG_Config::Features.AvoidAngleLlvmpipeSamplerMipmapMinFilter;
MGLOG_I(" GL_EXT_disjoint_timer_query supported: %s",
caps.SupportsDisjointTimerQuery ? "true" : "false");
MGLOG_I(" ANGLE renderer: %s", caps.IsAngleRenderer ? "true" : "false");
MGLOG_I(" ANGLE llvmpipe renderer: %s", caps.IsAngleLlvmpipeRenderer ? "true" : "false");
MGLOG_I(" Avoid sampler mipmap min filter: %s",
caps.AvoidSamplerMipmapMinFilter ? "true" : "false");
return true;
}
} // namespace MobileGL::MG_Util::BackendLoader
@@ -611,6 +611,8 @@ namespace MobileGL {
GLbitfield flags)
GL_FUNC_TYPEDEF(void, glGetQueryObjectivEXT, GLuint id, GLenum pname, GLint* params)
GL_FUNC_TYPEDEF(void, glGetQueryObjecti64vEXT, GLuint id, GLenum pname, GLint64* params)
GL_FUNC_TYPEDEF(void, glQueryCounterEXT, GLuint id, GLenum target)
GL_FUNC_TYPEDEF(void, glGetQueryObjectui64vEXT, GLuint id, GLenum pname, GLuint64* params)
GL_FUNC_TYPEDEF(void, glBindFragDataLocationEXT, GLuint program, GLuint colorNumber, const GLchar* name)
GL_FUNC_TYPEDEF(void*, glMapBufferOES, GLenum target, GLenum access)
@@ -1000,6 +1002,8 @@ namespace MobileGL {
GL_FUNC_DECL(glBufferStorageEXT)
GL_FUNC_DECL(glGetQueryObjectivEXT)
GL_FUNC_DECL(glGetQueryObjecti64vEXT)
GL_FUNC_DECL(glQueryCounterEXT)
GL_FUNC_DECL(glGetQueryObjectui64vEXT)
GL_FUNC_DECL(glBindFragDataLocationEXT)
GL_FUNC_DECL(glMapBufferOES)
@@ -1019,6 +1023,16 @@ namespace MobileGL {
Bool SupportsPersistentMapping = false;
Bool SupportsNorm16Texture = false;
Bool SupportsBaseInstance = false;
// GL_EXT_disjoint_timer_query is present in the extension string.
Bool SupportsDisjointTimerQuery = false;
// GL_RENDERER contains "ANGLE".
Bool IsAngleRenderer = false;
// GL_RENDERER contains both "ANGLE" and "llvmpipe".
Bool IsAngleLlvmpipeRenderer = false;
// IsAngleLlvmpipeRenderer combined with the
// MOBILEGL_ANGLE_LLVMPIPE_AVOID_SAMPLER_MIPMAP_MIN_FILTER feature toggle:
// sampler min filters should drop their mipmap component.
Bool AvoidSamplerMipmapMinFilter = false;
// True when indirect draws leak the command's baseInstance word ("reserved,
// must be zero" in unextended ES) into gl_InstanceID. Conforming ES drivers
// keep gl_InstanceID zero-based; ANGLE's Vulkan backend hands the command