// MobileGL - MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "Loader.h" #include "MG_Util/Types.h" #include #include #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif #include #else #include #endif namespace MobileGL::MG_Util::BackendLoader { static Bool UseAngle() { return MG_Config::Features.UseAngle; } #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) static Bool IsTraceAngleLibrary(const String& name) { return name == "libGLESv2_angle.so" || name == "libEGL_angle.so"; } static Bool IsAllowedTraceAngleVariant(const String& variant) { return variant == "ec889e6ea831" || variant == "90a62123d794"; } static Bool ResolveTraceAngleLibraryPath(const String& name, String& path) { const String& variant = MG_Config::Features.TraceAngleVariant; if (!IsAllowedTraceAngleVariant(variant)) { MGLOG_F("Rejected trace ANGLE variant '%s'", variant.c_str()); return false; } Dl_info mobileGlInfo{}; if (dladdr(reinterpret_cast(&ResolveTraceAngleLibraryPath), &mobileGlInfo) == 0 || mobileGlInfo.dli_fname == nullptr) { MGLOG_F("Failed to resolve signed trace native library directory"); return false; } String nativeLibraryPath = mobileGlInfo.dli_fname; const SizeT separator = nativeLibraryPath.find_last_of('/'); if (separator == String::npos) { MGLOG_F("Invalid MobileGL library path: %s", nativeLibraryPath.c_str()); return false; } const SizeT extension = name.rfind(".so"); if (extension == String::npos) { MGLOG_F("Invalid trace ANGLE library name: %s", name.c_str()); return false; } path = nativeLibraryPath.substr(0, separator + 1) + name.substr(0, extension) + "_" + variant + ".so"; return true; } #endif static void* OpenLib(const Vector& names) { #if defined(_WIN32) for (const auto& name : names) { if (HMODULE lib = LoadLibraryA(name.c_str())) { MGLOG_I("Loaded GL backend library: %s", name.c_str()); return reinterpret_cast(lib); } } #elif !defined(__APPLE__) || defined(MOBILEGL_IOS) static const String LibPathPrefixes[] = { #if defined(MOBILEGL_IOS) "@rpath/", "@executable_path/Frameworks/", "@loader_path/Frameworks/", #else "/opt/vc/lib/", "/usr/local/lib/", "/usr/lib/", "/usr/lib/x86_64-linux-gnu/", "/usr/lib64/", "/lib64/", #endif "" // Keep this last so the dynamic loader can use LD_LIBRARY_PATH. }; void* lib = nullptr; Int flags = RTLD_LOCAL | RTLD_NOW; for (const auto& prefix : LibPathPrefixes) { for (const auto& name : names) { #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) if (UseAngle() && IsTraceAngleLibrary(name)) { String signedPath; if (!ResolveTraceAngleLibraryPath(name, signedPath)) { return nullptr; } lib = dlopen(signedPath.c_str(), flags); if (lib == nullptr) { MGLOG_F("Failed to open signed trace ANGLE library %s: %s", signedPath.c_str(), dlerror()); return nullptr; } MGLOG_I("Loaded signed trace ANGLE library: %s", signedPath.c_str()); return lib; } #endif String path_name = prefix + name; if ((lib = dlopen(path_name.c_str(), flags))) { MGLOG_I("Loaded GL backend library: %s", path_name.c_str()); return lib; } } } #endif return nullptr; } inline void* ProcAddress(void* lib, const char* name) { #if defined(_WIN32) return reinterpret_cast(::GetProcAddress(reinterpret_cast(lib), name)); #elif !defined(__APPLE__) || defined(MOBILEGL_IOS) return dlsym(lib, name); #else return nullptr; #endif } void AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, MG_External::EGL::eglGetProcAddress_PTR procAddress) { if (!procAddress) { MGLOG_E("eglGetProcAddress is nullptr, cannot load GLES functions"); return; } #define INIT_GLES_FUNC(name) \ do { \ funcs.name = (MG_External::GLES::name##_PTR)procAddress(#name); \ if (!funcs.name) { \ MGLOG_E("Failed to load GLES function: %s", #name); \ } \ } while (0); // Optional (extension-provided) entry points: a null pointer is expected on drivers that lack the // extension, so absence is not an error. The call site null-checks before use. #define INIT_GLES_FUNC_OPTIONAL(name) \ do { \ funcs.name = (MG_External::GLES::name##_PTR)procAddress(#name); \ } while (0); { INIT_GLES_FUNC(glActiveTexture) INIT_GLES_FUNC(glAttachShader) INIT_GLES_FUNC(glBindAttribLocation) INIT_GLES_FUNC(glBindBuffer) INIT_GLES_FUNC(glBindFramebuffer) INIT_GLES_FUNC(glBindRenderbuffer) INIT_GLES_FUNC(glBindTexture) INIT_GLES_FUNC(glBlendColor) INIT_GLES_FUNC(glBlendEquation) INIT_GLES_FUNC(glBlendEquationSeparate) INIT_GLES_FUNC(glBlendFunc) INIT_GLES_FUNC(glBlendFuncSeparate) INIT_GLES_FUNC(glBufferData) INIT_GLES_FUNC(glBufferSubData) INIT_GLES_FUNC(glCheckFramebufferStatus) INIT_GLES_FUNC(glClear) INIT_GLES_FUNC(glClearColor) INIT_GLES_FUNC(glClearDepthf) INIT_GLES_FUNC(glClearStencil) INIT_GLES_FUNC(glColorMask) INIT_GLES_FUNC(glCompileShader) INIT_GLES_FUNC(glCompressedTexImage2D) INIT_GLES_FUNC(glCompressedTexSubImage2D) // INIT_GLES_FUNC(glCopyTexImage1D) INIT_GLES_FUNC(glCopyTexImage2D) INIT_GLES_FUNC(glCopyTexSubImage2D) INIT_GLES_FUNC(glCreateProgram) INIT_GLES_FUNC(glCreateShader) INIT_GLES_FUNC(glCullFace) INIT_GLES_FUNC(glDeleteBuffers) INIT_GLES_FUNC(glDeleteFramebuffers) INIT_GLES_FUNC(glDeleteProgram) INIT_GLES_FUNC(glDeleteRenderbuffers) INIT_GLES_FUNC(glDeleteShader) INIT_GLES_FUNC(glDeleteTextures) INIT_GLES_FUNC(glDepthFunc) INIT_GLES_FUNC(glDepthMask) INIT_GLES_FUNC(glDepthRangef) INIT_GLES_FUNC(glDetachShader) INIT_GLES_FUNC(glDisable) INIT_GLES_FUNC(glDisableVertexAttribArray) INIT_GLES_FUNC(glDrawArrays) INIT_GLES_FUNC(glDrawElements) INIT_GLES_FUNC(glEnable) INIT_GLES_FUNC(glEnableVertexAttribArray) INIT_GLES_FUNC(glFinish) INIT_GLES_FUNC(glFlush) INIT_GLES_FUNC(glFramebufferRenderbuffer) INIT_GLES_FUNC(glFramebufferTexture2D) INIT_GLES_FUNC(glFrontFace) INIT_GLES_FUNC(glGenBuffers) INIT_GLES_FUNC(glGenerateMipmap) INIT_GLES_FUNC(glGenFramebuffers) INIT_GLES_FUNC(glGenRenderbuffers) INIT_GLES_FUNC(glGenTextures) INIT_GLES_FUNC(glGetActiveAttrib) INIT_GLES_FUNC(glGetActiveUniform) INIT_GLES_FUNC(glGetAttachedShaders) INIT_GLES_FUNC(glGetAttribLocation) INIT_GLES_FUNC(glGetBooleanv) INIT_GLES_FUNC(glGetBufferParameteriv) INIT_GLES_FUNC(glGetError) INIT_GLES_FUNC(glGetString) INIT_GLES_FUNC(glGetStringi) INIT_GLES_FUNC(glGetFloatv) INIT_GLES_FUNC(glGetFramebufferAttachmentParameteriv) INIT_GLES_FUNC(glGetIntegerv) INIT_GLES_FUNC(glGetProgramiv) INIT_GLES_FUNC(glGetProgramInfoLog) INIT_GLES_FUNC(glGetRenderbufferParameteriv) INIT_GLES_FUNC(glGetShaderiv) INIT_GLES_FUNC(glGetShaderInfoLog) INIT_GLES_FUNC(glGetShaderPrecisionFormat) INIT_GLES_FUNC(glGetShaderSource) INIT_GLES_FUNC(glGetTexParameterfv) INIT_GLES_FUNC(glGetTexParameteriv) INIT_GLES_FUNC(glGetUniformfv) INIT_GLES_FUNC(glGetUniformiv) INIT_GLES_FUNC(glGetUniformLocation) INIT_GLES_FUNC(glGetVertexAttribfv) INIT_GLES_FUNC(glGetVertexAttribiv) INIT_GLES_FUNC(glGetVertexAttribPointerv) INIT_GLES_FUNC(glHint) INIT_GLES_FUNC(glIsBuffer) INIT_GLES_FUNC(glIsEnabled) INIT_GLES_FUNC(glIsFramebuffer) INIT_GLES_FUNC(glIsProgram) INIT_GLES_FUNC(glIsRenderbuffer) INIT_GLES_FUNC(glIsShader) INIT_GLES_FUNC(glIsTexture) INIT_GLES_FUNC(glLineWidth) INIT_GLES_FUNC(glLinkProgram) INIT_GLES_FUNC(glLogicOp) INIT_GLES_FUNC(glPointSize) INIT_GLES_FUNC(glPixelStorei) INIT_GLES_FUNC(glPolygonOffset) INIT_GLES_FUNC(glReadPixels) INIT_GLES_FUNC(glReleaseShaderCompiler) INIT_GLES_FUNC(glRenderbufferStorage) INIT_GLES_FUNC(glSampleCoverage) INIT_GLES_FUNC(glScissor) INIT_GLES_FUNC(glShaderBinary) INIT_GLES_FUNC(glShaderSource) INIT_GLES_FUNC(glStencilFunc) INIT_GLES_FUNC(glStencilFuncSeparate) INIT_GLES_FUNC(glStencilMask) INIT_GLES_FUNC(glStencilMaskSeparate) INIT_GLES_FUNC(glStencilOp) INIT_GLES_FUNC(glStencilOpSeparate) // INIT_GLES_FUNC(glTexImage1D) INIT_GLES_FUNC(glTexImage2D) // INIT_GLES_FUNC(glTexStorage1D) INIT_GLES_FUNC(glTexParameterf) INIT_GLES_FUNC(glTexParameterfv) INIT_GLES_FUNC(glTexParameteri) INIT_GLES_FUNC(glTexParameteriv) INIT_GLES_FUNC(glTexSubImage2D) INIT_GLES_FUNC(glUniform1f) INIT_GLES_FUNC(glUniform1fv) INIT_GLES_FUNC(glUniform1i) INIT_GLES_FUNC(glUniform1iv) INIT_GLES_FUNC(glUniform2f) INIT_GLES_FUNC(glUniform2fv) INIT_GLES_FUNC(glUniform2i) INIT_GLES_FUNC(glUniform2iv) INIT_GLES_FUNC(glUniform3f) INIT_GLES_FUNC(glUniform3fv) INIT_GLES_FUNC(glUniform3i) INIT_GLES_FUNC(glUniform3iv) INIT_GLES_FUNC(glUniform4f) INIT_GLES_FUNC(glUniform4fv) INIT_GLES_FUNC(glUniform4i) INIT_GLES_FUNC(glUniform4iv) INIT_GLES_FUNC(glUniformMatrix2fv) INIT_GLES_FUNC(glUniformMatrix3fv) INIT_GLES_FUNC(glUniformMatrix4fv) INIT_GLES_FUNC(glUseProgram) INIT_GLES_FUNC(glValidateProgram) INIT_GLES_FUNC(glVertexAttrib1f) INIT_GLES_FUNC(glVertexAttrib1fv) INIT_GLES_FUNC(glVertexAttrib2f) INIT_GLES_FUNC(glVertexAttrib2fv) INIT_GLES_FUNC(glVertexAttrib3f) INIT_GLES_FUNC(glVertexAttrib3fv) INIT_GLES_FUNC(glVertexAttrib4f) INIT_GLES_FUNC(glVertexAttrib4fv) INIT_GLES_FUNC(glVertexAttribPointer) INIT_GLES_FUNC(glViewport) INIT_GLES_FUNC(glReadBuffer) INIT_GLES_FUNC(glDrawRangeElements) INIT_GLES_FUNC(glTexImage3D) INIT_GLES_FUNC(glTexSubImage3D) INIT_GLES_FUNC(glCopyTexSubImage3D) INIT_GLES_FUNC(glCompressedTexImage3D) INIT_GLES_FUNC(glCompressedTexSubImage3D) INIT_GLES_FUNC(glGenQueries) INIT_GLES_FUNC(glDeleteQueries) INIT_GLES_FUNC(glIsQuery) INIT_GLES_FUNC(glBeginQuery) INIT_GLES_FUNC(glEndQuery) INIT_GLES_FUNC(glGetQueryiv) INIT_GLES_FUNC(glGetQueryObjectuiv) INIT_GLES_FUNC(glUnmapBuffer) INIT_GLES_FUNC(glGetBufferPointerv) INIT_GLES_FUNC(glDrawBuffers) INIT_GLES_FUNC(glUniformMatrix2x3fv) INIT_GLES_FUNC(glUniformMatrix3x2fv) INIT_GLES_FUNC(glUniformMatrix2x4fv) INIT_GLES_FUNC(glUniformMatrix4x2fv) INIT_GLES_FUNC(glUniformMatrix3x4fv) INIT_GLES_FUNC(glUniformMatrix4x3fv) INIT_GLES_FUNC(glBlitFramebuffer) INIT_GLES_FUNC(glRenderbufferStorageMultisample) INIT_GLES_FUNC(glFramebufferTextureLayer) INIT_GLES_FUNC(glFlushMappedBufferRange) INIT_GLES_FUNC(glBindVertexArray) INIT_GLES_FUNC(glDeleteVertexArrays) INIT_GLES_FUNC(glGenVertexArrays) INIT_GLES_FUNC(glIsVertexArray) INIT_GLES_FUNC(glGetIntegeri_v) INIT_GLES_FUNC(glBeginTransformFeedback) INIT_GLES_FUNC(glEndTransformFeedback) INIT_GLES_FUNC(glBindBufferRange) INIT_GLES_FUNC(glBindBufferBase) INIT_GLES_FUNC(glTransformFeedbackVaryings) INIT_GLES_FUNC(glGetTransformFeedbackVarying) INIT_GLES_FUNC(glVertexAttribIPointer) INIT_GLES_FUNC(glGetVertexAttribIiv) INIT_GLES_FUNC(glGetVertexAttribIuiv) INIT_GLES_FUNC(glVertexAttribI4i) INIT_GLES_FUNC(glVertexAttribI4ui) INIT_GLES_FUNC(glVertexAttribI4iv) INIT_GLES_FUNC(glVertexAttribI4uiv) INIT_GLES_FUNC(glGetUniformuiv) INIT_GLES_FUNC(glGetFragDataLocation) INIT_GLES_FUNC(glUniform1ui) INIT_GLES_FUNC(glUniform2ui) INIT_GLES_FUNC(glUniform3ui) INIT_GLES_FUNC(glUniform4ui) INIT_GLES_FUNC(glUniform1uiv) INIT_GLES_FUNC(glUniform2uiv) INIT_GLES_FUNC(glUniform3uiv) INIT_GLES_FUNC(glUniform4uiv) INIT_GLES_FUNC(glClearBufferiv) INIT_GLES_FUNC(glClearBufferuiv) INIT_GLES_FUNC(glClearBufferfv) INIT_GLES_FUNC(glClearBufferfi) INIT_GLES_FUNC(glCopyBufferSubData) INIT_GLES_FUNC(glGetUniformIndices) INIT_GLES_FUNC(glGetActiveUniformsiv) INIT_GLES_FUNC(glGetUniformBlockIndex) INIT_GLES_FUNC(glGetActiveUniformBlockiv) INIT_GLES_FUNC(glGetActiveUniformBlockName) INIT_GLES_FUNC(glUniformBlockBinding) INIT_GLES_FUNC(glDrawArraysInstanced) INIT_GLES_FUNC(glDrawElementsInstanced) INIT_GLES_FUNC(glFenceSync) INIT_GLES_FUNC(glIsSync) INIT_GLES_FUNC(glDeleteSync) INIT_GLES_FUNC(glClientWaitSync) INIT_GLES_FUNC(glWaitSync) INIT_GLES_FUNC(glGetInteger64v) INIT_GLES_FUNC(glGetSynciv) INIT_GLES_FUNC(glGetInteger64i_v) INIT_GLES_FUNC(glGetBufferParameteri64v) INIT_GLES_FUNC(glGenSamplers) INIT_GLES_FUNC(glDeleteSamplers) INIT_GLES_FUNC(glIsSampler) INIT_GLES_FUNC(glBindSampler) INIT_GLES_FUNC(glSamplerParameteri) INIT_GLES_FUNC(glSamplerParameteriv) INIT_GLES_FUNC(glSamplerParameterf) INIT_GLES_FUNC(glSamplerParameterfv) INIT_GLES_FUNC(glGetSamplerParameteriv) INIT_GLES_FUNC(glGetSamplerParameterfv) INIT_GLES_FUNC(glVertexAttribDivisor) INIT_GLES_FUNC(glBindTransformFeedback) INIT_GLES_FUNC(glDeleteTransformFeedbacks) INIT_GLES_FUNC(glGenTransformFeedbacks) INIT_GLES_FUNC(glIsTransformFeedback) INIT_GLES_FUNC(glPauseTransformFeedback) INIT_GLES_FUNC(glResumeTransformFeedback) INIT_GLES_FUNC(glGetProgramBinary) INIT_GLES_FUNC(glProgramBinary) INIT_GLES_FUNC(glProgramParameteri) INIT_GLES_FUNC(glInvalidateFramebuffer) INIT_GLES_FUNC(glInvalidateSubFramebuffer) INIT_GLES_FUNC(glTexStorage2D) INIT_GLES_FUNC(glTexStorage3D) INIT_GLES_FUNC(glGetInternalformativ) INIT_GLES_FUNC(glDispatchCompute) INIT_GLES_FUNC(glDispatchComputeIndirect) INIT_GLES_FUNC(glDrawArraysIndirect) INIT_GLES_FUNC(glDrawElementsIndirect) INIT_GLES_FUNC(glFramebufferParameteri) INIT_GLES_FUNC(glGetFramebufferParameteriv) INIT_GLES_FUNC(glGetProgramInterfaceiv) INIT_GLES_FUNC(glShaderStorageBlockBinding) INIT_GLES_FUNC(glGetProgramResourceIndex) INIT_GLES_FUNC(glGetProgramResourceName) INIT_GLES_FUNC(glGetProgramResourceiv) INIT_GLES_FUNC(glGetProgramResourceLocation) INIT_GLES_FUNC(glUseProgramStages) INIT_GLES_FUNC(glActiveShaderProgram) INIT_GLES_FUNC(glCreateShaderProgramv) INIT_GLES_FUNC(glBindProgramPipeline) INIT_GLES_FUNC(glDeleteProgramPipelines) INIT_GLES_FUNC(glGenProgramPipelines) INIT_GLES_FUNC(glIsProgramPipeline) INIT_GLES_FUNC(glGetProgramPipelineiv) INIT_GLES_FUNC(glProgramUniform1i) INIT_GLES_FUNC(glProgramUniform2i) INIT_GLES_FUNC(glProgramUniform3i) INIT_GLES_FUNC(glProgramUniform4i) INIT_GLES_FUNC(glProgramUniform1ui) INIT_GLES_FUNC(glProgramUniform2ui) INIT_GLES_FUNC(glProgramUniform3ui) INIT_GLES_FUNC(glProgramUniform4ui) INIT_GLES_FUNC(glProgramUniform1f) INIT_GLES_FUNC(glProgramUniform2f) INIT_GLES_FUNC(glProgramUniform3f) INIT_GLES_FUNC(glProgramUniform4f) INIT_GLES_FUNC(glProgramUniform1iv) INIT_GLES_FUNC(glProgramUniform2iv) INIT_GLES_FUNC(glProgramUniform3iv) INIT_GLES_FUNC(glProgramUniform4iv) INIT_GLES_FUNC(glProgramUniform1uiv) INIT_GLES_FUNC(glProgramUniform2uiv) INIT_GLES_FUNC(glProgramUniform3uiv) INIT_GLES_FUNC(glProgramUniform4uiv) INIT_GLES_FUNC(glProgramUniform1fv) INIT_GLES_FUNC(glProgramUniform2fv) INIT_GLES_FUNC(glProgramUniform3fv) INIT_GLES_FUNC(glProgramUniform4fv) INIT_GLES_FUNC(glProgramUniformMatrix2fv) INIT_GLES_FUNC(glProgramUniformMatrix3fv) INIT_GLES_FUNC(glProgramUniformMatrix4fv) INIT_GLES_FUNC(glProgramUniformMatrix2x3fv) INIT_GLES_FUNC(glProgramUniformMatrix3x2fv) INIT_GLES_FUNC(glProgramUniformMatrix2x4fv) INIT_GLES_FUNC(glProgramUniformMatrix4x2fv) INIT_GLES_FUNC(glProgramUniformMatrix3x4fv) INIT_GLES_FUNC(glProgramUniformMatrix4x3fv) INIT_GLES_FUNC(glValidateProgramPipeline) INIT_GLES_FUNC(glGetProgramPipelineInfoLog) INIT_GLES_FUNC(glBindImageTexture) INIT_GLES_FUNC(glGetBooleani_v) INIT_GLES_FUNC(glMemoryBarrier) INIT_GLES_FUNC(glMemoryBarrierByRegion) INIT_GLES_FUNC(glTexStorage2DMultisample) INIT_GLES_FUNC(glGetMultisamplefv) INIT_GLES_FUNC(glSampleMaski) INIT_GLES_FUNC(glGetTexLevelParameteriv) INIT_GLES_FUNC(glGetTexLevelParameterfv) INIT_GLES_FUNC(glBindVertexBuffer) INIT_GLES_FUNC(glVertexAttribFormat) INIT_GLES_FUNC(glVertexAttribIFormat) INIT_GLES_FUNC(glVertexAttribBinding) INIT_GLES_FUNC(glVertexBindingDivisor) INIT_GLES_FUNC(glBlendBarrier) INIT_GLES_FUNC(glCopyImageSubData) INIT_GLES_FUNC(glDebugMessageControl) INIT_GLES_FUNC(glDebugMessageInsert) INIT_GLES_FUNC(glDebugMessageCallback) INIT_GLES_FUNC(glGetDebugMessageLog) INIT_GLES_FUNC(glPushDebugGroup) INIT_GLES_FUNC(glPopDebugGroup) INIT_GLES_FUNC(glObjectLabel) INIT_GLES_FUNC(glGetObjectLabel) INIT_GLES_FUNC(glObjectPtrLabel) INIT_GLES_FUNC(glGetObjectPtrLabel) INIT_GLES_FUNC(glGetPointerv) INIT_GLES_FUNC(glEnablei) INIT_GLES_FUNC(glDisablei) INIT_GLES_FUNC(glBlendEquationi) INIT_GLES_FUNC(glBlendEquationSeparatei) INIT_GLES_FUNC(glBlendFunci) INIT_GLES_FUNC(glBlendFuncSeparatei) INIT_GLES_FUNC(glColorMaski) INIT_GLES_FUNC(glIsEnabledi) INIT_GLES_FUNC(glDrawElementsBaseVertex) INIT_GLES_FUNC(glDrawRangeElementsBaseVertex) INIT_GLES_FUNC(glDrawElementsInstancedBaseVertex) INIT_GLES_FUNC(glFramebufferTexture) INIT_GLES_FUNC(glPrimitiveBoundingBox) INIT_GLES_FUNC(glGetGraphicsResetStatus) INIT_GLES_FUNC(glReadnPixels) INIT_GLES_FUNC(glGetnUniformfv) INIT_GLES_FUNC(glGetnUniformiv) INIT_GLES_FUNC(glGetnUniformuiv) INIT_GLES_FUNC(glMinSampleShading) INIT_GLES_FUNC(glPatchParameteri) INIT_GLES_FUNC(glTexParameterIiv) INIT_GLES_FUNC(glTexParameterIuiv) INIT_GLES_FUNC(glGetTexParameterIiv) INIT_GLES_FUNC(glGetTexParameterIuiv) INIT_GLES_FUNC(glSamplerParameterIiv) INIT_GLES_FUNC(glSamplerParameterIuiv) INIT_GLES_FUNC(glGetSamplerParameterIiv) INIT_GLES_FUNC(glGetSamplerParameterIuiv) INIT_GLES_FUNC(glTexBuffer) INIT_GLES_FUNC(glTexBufferRange) // Optional: absent on an ES 3.2 core driver, and absent on ES 3.1 without the // matching extension. The tier resolution below picks whichever spelling the // driver's own support actually comes from. // Optional by nature: ES never made texture views core, so both spellings are // absent on plenty of drivers and neither absence is an error. INIT_GLES_FUNC_OPTIONAL(glTextureViewEXT) INIT_GLES_FUNC_OPTIONAL(glTextureViewOES) INIT_GLES_FUNC_OPTIONAL(glTexBufferEXT) INIT_GLES_FUNC_OPTIONAL(glTexBufferOES) INIT_GLES_FUNC_OPTIONAL(glTexBufferRangeEXT) INIT_GLES_FUNC_OPTIONAL(glTexBufferRangeOES) INIT_GLES_FUNC(glTexStorage3DMultisample) INIT_GLES_FUNC(glMapBufferRange) 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_OPTIONAL(glPolygonModeNV) INIT_GLES_FUNC_OPTIONAL(glPolygonModeANGLE) INIT_GLES_FUNC_OPTIONAL(glColorMaskiEXT) INIT_GLES_FUNC_OPTIONAL(glColorMaskiOES) // Extension-only multi-draw entry points. eglGetProcAddress may legally return a // non-NULL stub for these on drivers that do not implement them (NVIDIA's ES driver // returns one for glMultiDrawElementsBaseVertexEXT that silently drops every draw), // so pointer presence proves nothing: callers must gate on the extension-derived // SupportsMultiDrawIndirect / SupportsMultiDrawElementsBaseVertex capability flags, // never on these pointers alone. INIT_GLES_FUNC_OPTIONAL(glMultiDrawArraysIndirectEXT) INIT_GLES_FUNC_OPTIONAL(glMultiDrawElementsIndirectEXT) INIT_GLES_FUNC_OPTIONAL(glMultiDrawElementsBaseVertexEXT) INIT_GLES_FUNC_OPTIONAL(glDrawArraysInstancedBaseInstanceEXT) INIT_GLES_FUNC_OPTIONAL(glDrawElementsInstancedBaseInstanceEXT) INIT_GLES_FUNC_OPTIONAL(glDrawElementsInstancedBaseVertexBaseInstanceEXT) } } void AcquireEGLFunctions(MG_External::EGLFunctionsTable& funcs) { void* eglLib = nullptr; #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) void* angleGlesLib = nullptr; #endif #if defined(_WIN32) // ANGLE is the GLES provider on Windows regardless of UseAngle(). Preload // libGLESv2.dll so libEGL.dll resolves its dependency from the same directory. if (!OpenLib({"libGLESv2.dll"})) { MGLOG_E("Failed to open ANGLE libGLESv2.dll"); return; } eglLib = OpenLib({"libEGL.dll"}); #else if (UseAngle()) { void* glesLib = OpenLib({"libGLESv2_angle.so"}); if (!glesLib) { MGLOG_E("Failed to open ANGLE libGLESv2_angle.so"); return; } #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) angleGlesLib = glesLib; #endif eglLib = OpenLib({"libEGL_angle.so"}); if (!eglLib) { MGLOG_E("Failed to open ANGLE libEGL_angle.so"); return; } } else { #if defined(MOBILEGL_IOS) eglLib = OpenLib({"libtinygl4angle.dylib"}); #else // Versioned SONAME first. The unversioned "libEGL.so" is a development // symlink: it ships in libegl-dev/mesa-libEGL-devel, NOT in the runtime // package, so a machine that can run GL perfectly well may not have it - // every stock Ubuntu/Debian runtime image, the GitHub Actions runners // included. Asking only for the unversioned name there makes dlopen fail, // which used to leave the whole EGL function table null and take the next // call through a null pointer (SIGSEGV inside InitDisplayAndContext). // Developer machines have both names, which is exactly why this only ever // showed up in CI. eglLib = OpenLib({"libEGL.so.1", "libEGL.so"}); #endif } #endif // !_WIN32 if (!eglLib) { // MGLOG_F, not MGLOG_E: with no EGL there is no rendering at all, so this is a // bring-up abort rather than a recoverable error. It was forced to F while the // Log.h ordering compiled MGLOG_E out of every shipping and CI build; F is still // the right level on its own merits, so it stays. MGLOG_F("Failed to open EGL library: none of libEGL.so.1 / libEGL.so could be " "dlopened; every EGL entry point will be null"); return; } auto resolveEGLProc = [&](const char* name) -> void* { #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) if (UseAngle()) { // Avoid the wrapper's canonical libGLESv2_angle.so lookup: // resolve its forwarding target from the verified variant. String target = "EGL_"; target += name + 3; return ProcAddress(angleGlesLib, target.c_str()); } #endif return ProcAddress(eglLib, name); }; #define INIT_EGL_FUNC(name) \ do { \ funcs.name = (MG_External::EGL::name##_PTR)resolveEGLProc(#name); \ if (!funcs.name) { \ /* MGLOG_F for the same reason as the open failure above: a null entry */ \ /* point is a crash waiting for its first caller, and MGLOG_E is compiled */ \ /* out at the INFO level every shipping and CI build uses. */ \ MGLOG_F("Failed to load EGL function: %s", #name); \ } \ } while (0); { INIT_EGL_FUNC(eglBindAPI) INIT_EGL_FUNC(eglBindTexImage) INIT_EGL_FUNC(eglChooseConfig) INIT_EGL_FUNC(eglCopyBuffers) INIT_EGL_FUNC(eglCreateContext) INIT_EGL_FUNC(eglCreatePbufferFromClientBuffer) INIT_EGL_FUNC(eglCreatePbufferSurface) INIT_EGL_FUNC(eglCreatePixmapSurface) INIT_EGL_FUNC(eglCreatePlatformPixmapSurface) INIT_EGL_FUNC(eglCreatePlatformWindowSurface) INIT_EGL_FUNC(eglCreateWindowSurface) INIT_EGL_FUNC(eglDestroyContext) INIT_EGL_FUNC(eglDestroySurface) INIT_EGL_FUNC(eglGetConfigAttrib) INIT_EGL_FUNC(eglGetConfigs) INIT_EGL_FUNC(eglGetCurrentContext) INIT_EGL_FUNC(eglGetCurrentDisplay) INIT_EGL_FUNC(eglGetCurrentSurface) INIT_EGL_FUNC(eglGetDisplay) INIT_EGL_FUNC(eglGetPlatformDisplay) INIT_EGL_FUNC(eglGetError) INIT_EGL_FUNC(eglGetProcAddress) INIT_EGL_FUNC(eglInitialize) INIT_EGL_FUNC(eglMakeCurrent) INIT_EGL_FUNC(eglQueryAPI) INIT_EGL_FUNC(eglQueryContext) INIT_EGL_FUNC(eglQueryString) INIT_EGL_FUNC(eglQuerySurface) INIT_EGL_FUNC(eglReleaseTexImage) INIT_EGL_FUNC(eglReleaseThread) INIT_EGL_FUNC(eglSurfaceAttrib) INIT_EGL_FUNC(eglSwapBuffers) INIT_EGL_FUNC(eglSwapBuffersWithDamageEXT) INIT_EGL_FUNC(eglSwapInterval) INIT_EGL_FUNC(eglTerminate) INIT_EGL_FUNC(eglUnlockSurfaceKHR) INIT_EGL_FUNC(eglWaitClient) INIT_EGL_FUNC(eglWaitGL) INIT_EGL_FUNC(eglWaitNative) INIT_EGL_FUNC(eglCreateSync) INIT_EGL_FUNC(eglDestroySync) INIT_EGL_FUNC(eglClientWaitSync) INIT_EGL_FUNC(eglGetSyncAttrib) INIT_EGL_FUNC(eglCreateImage) INIT_EGL_FUNC(eglDestroyImage) INIT_EGL_FUNC(eglGetPlatformDisplay) INIT_EGL_FUNC(eglWaitSync) } } // Detects whether 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, but ANGLE's Vulkan backend forwards the command verbatim to // vkCmdDraw*Indirect and compiles gl_InstanceID to SPIR-V InstanceIndex, which includes // firstInstance. The DirectGLES native indirect-draw path uses this answer to keep // gl_InstanceID zero-based in rewritten shaders (PromoteDrawParameterGlobalsToUniforms). Bool ProbeIndirectInstanceIdIncludesBaseInstance(const MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& f) { const Bool esVersionOk = caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); if (!esVersionOk || !f.glDrawArraysIndirect || !f.glBindBufferBase || !f.glMapBufferRange || !f.glUnmapBuffer || !f.glMemoryBarrier || !f.glCreateShader || !f.glCreateProgram) { return false; } // Read from caps, not re-queried: the per-stage limits are resolved (and their query // errors drained) before this probe runs, so asking the driver again would be a second // round trip that can disagree with the number MobileGL actually advertises - and, on // the early return below, would leave its own GL_INVALID_ENUM in the queue for the // application's first glGetError to find. const GLint maxVertexSsboBlocks = caps.MaxVertexShaderStorageBlocks; if (maxVertexSsboBlocks < 1) { // The native indirect machinery cannot read the command buffer from the vertex // stage on this driver anyway; assume conforming zero-based gl_InstanceID. MGLOG_I("baseInstance probe skipped: GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = %d", maxVertexSsboBlocks); return false; } while (f.glGetError() != GL_NO_ERROR) { } const char* vsSource = "#version 310 es\n" "layout(std430, binding = 0) buffer MgProbeResult { highp int mg_probeValue; };\n" "void main() {\n" " mg_probeValue = gl_InstanceID;\n" " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" " gl_PointSize = 1.0;\n" "}\n"; const char* fsSource = "#version 310 es\n" "void main() {}\n"; const auto compileShader = [&f](GLenum type, const char* src) -> GLuint { const GLuint shader = f.glCreateShader(type); if (shader == 0) { return 0; } f.glShaderSource(shader, 1, &src, nullptr); f.glCompileShader(shader); GLint status = GL_FALSE; f.glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { f.glDeleteShader(shader); return 0; } return shader; }; const GLuint vs = compileShader(GL_VERTEX_SHADER, vsSource); const GLuint fs = compileShader(GL_FRAGMENT_SHADER, fsSource); GLuint program = 0; if (vs != 0 && fs != 0) { program = f.glCreateProgram(); if (program != 0) { f.glAttachShader(program, vs); f.glAttachShader(program, fs); f.glLinkProgram(program); GLint status = GL_FALSE; f.glGetProgramiv(program, GL_LINK_STATUS, &status); if (status != GL_TRUE) { f.glDeleteProgram(program); program = 0; } } } if (vs != 0) f.glDeleteShader(vs); if (fs != 0) f.glDeleteShader(fs); if (program == 0) { MGLOG_I("baseInstance probe skipped: probe program failed to build (vs=%u fs=%u)", vs, fs); while (f.glGetError() != GL_NO_ERROR) { } return false; } constexpr GLuint kProbeBaseInstance = 7; struct { GLuint count; GLuint instanceCount; GLuint first; GLuint baseInstance; } command = {1, 1, 0, kProbeBaseInstance}; const GLint sentinel = -1; // ES makes indirect draws INVALID_OPERATION on the default vertex array object. GLuint vao = 0; f.glGenVertexArrays(1, &vao); f.glBindVertexArray(vao); GLuint buffers[2] = {0, 0}; // [0] = result SSBO, [1] = indirect command buffer f.glGenBuffers(2, buffers); f.glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[0]); f.glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(sentinel), &sentinel, GL_STATIC_DRAW); f.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[0]); f.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffers[1]); f.glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(command), &command, GL_STATIC_DRAW); // The default framebuffer may be incomplete (e.g. surfaceless contexts) and draws // are validated against completeness even under GL_RASTERIZER_DISCARD, so give the // probe its own 1x1 target. GLuint framebuffer = 0; GLuint renderbuffer = 0; f.glGenFramebuffers(1, &framebuffer); f.glGenRenderbuffers(1, &renderbuffer); f.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); f.glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1); f.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); f.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); f.glUseProgram(program); f.glEnable(GL_RASTERIZER_DISCARD); f.glDrawArraysIndirect(GL_POINTS, nullptr); f.glDisable(GL_RASTERIZER_DISCARD); f.glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); Bool includesBase = false; const GLenum drawError = f.glGetError(); if (drawError == GL_NO_ERROR) { f.glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[0]); const void* mapped = f.glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLint), GL_MAP_READ_BIT); if (mapped != nullptr) { GLint written = -1; std::memcpy(&written, mapped, sizeof(written)); f.glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); includesBase = written == static_cast(kProbeBaseInstance); MGLOG_I("baseInstance probe: shader observed gl_InstanceID = %d (baseInstance was %u)", written, kProbeBaseInstance); } else { MGLOG_I("baseInstance probe inconclusive: result map failed"); } } else { MGLOG_I("baseInstance probe inconclusive: draw raised GL error 0x%x", drawError); } f.glUseProgram(0); f.glBindFramebuffer(GL_FRAMEBUFFER, 0); f.glBindRenderbuffer(GL_RENDERBUFFER, 0); f.glDeleteFramebuffers(1, &framebuffer); f.glDeleteRenderbuffers(1, &renderbuffer); f.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); f.glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); f.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); f.glBindVertexArray(0); f.glDeleteVertexArrays(1, &vao); f.glDeleteBuffers(2, buffers); f.glDeleteProgram(program); while (f.glGetError() != GL_NO_ERROR) { } return includesBase; } // GL 4.6 table 23.65 admits exactly four answers for GL_LAYER_PROVOKING_VERTEX and // GL_VIEWPORT_INDEX_PROVOKING_VERTEX. Anything else means the driver wrote something MobileGL // cannot forward as a convention, and GL_UNDEFINED_VERTEX - a legal answer, not a placeholder // - is the accurate thing to say about it. static GLenum NormalizeProvokingVertexConvention(GLint driverValue) { switch (static_cast(driverValue)) { case GL_FIRST_VERTEX_CONVENTION: case GL_LAST_VERTEX_CONVENTION: case GL_PROVOKING_VERTEX: case GL_UNDEFINED_VERTEX: return static_cast(driverValue); default: return GL_UNDEFINED_VERTEX; } } static const char* ProvokingVertexConventionName(GLenum convention) { switch (convention) { case GL_FIRST_VERTEX_CONVENTION: return "GL_FIRST_VERTEX_CONVENTION"; case GL_LAST_VERTEX_CONVENTION: return "GL_LAST_VERTEX_CONVENTION"; case GL_PROVOKING_VERTEX: return "GL_PROVOKING_VERTEX"; default: return "GL_UNDEFINED_VERTEX"; } } Bool FillInGLESCapabilities(MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& glesFuncs) { if (!glesFuncs.glGetString || !glesFuncs.glGetIntegerv) { MGLOG_E("Required GLES functions are not loaded, cannot query capabilities"); return false; } auto* vendorName = glesFuncs.glGetString(GL_VENDOR); MGLOG_I("GL_VENDOR: %s", vendorName); auto* gpuName = glesFuncs.glGetString(GL_RENDERER); MGLOG_I("GL_RENDERER: %s", gpuName); glesFuncs.glGetIntegerv(GL_MAJOR_VERSION, &caps.GLESVersion.Major); glesFuncs.glGetIntegerv(GL_MINOR_VERSION, &caps.GLESVersion.Minor); caps.GLESVersionString = String((char*)glesFuncs.glGetString(GL_VERSION)); caps.GLESRendererString = String((char*)gpuName); caps.GLESVendorString = String((char*)vendorName); caps.GLESShadingLanguageVersionString = String((char*)glesFuncs.glGetString(GL_SHADING_LANGUAGE_VERSION)); GLint extCount = 0; glesFuncs.glGetIntegerv(GL_NUM_EXTENSIONS, &extCount); MGLOG_I("Detected %d OpenGL ES extensions:", extCount); // Combined below: glMultiDrawElementsBaseVertexEXT exists only where EXT/OES // draw_elements_base_vertex interacts with GL_EXT_multi_draw_arrays. Bool hasMultiDrawIndirectExtension = false; Bool hasDrawElementsBaseVertexExtension = false; Bool hasMultiDrawArraysExtension = false; // Resolved into caps.TextureBufferSupport below, once the ES version is also known. Bool hasExtTextureBuffer = false; Bool hasOesTextureBuffer = false; // Resolved into caps.SupportsTextureView below. Two spellings of one extension; the // entry points differ only in suffix, so unlike the buffer-texture tier there is nothing // downstream that needs to know WHICH one answered. Bool hasExtTextureView = false; Bool hasOesTextureView = false; // Combined with the three entry points below; DirectGLES emulates baseInstance when this // comes out false, so a stub pointer counting as support would silently break the draws. Bool hasBaseInstanceExtension = false; for (GLint i = 0; i < extCount; ++i) { const char* extension = (const char*)glesFuncs.glGetStringi(GL_EXTENSIONS, i); if (extension) { MGLOG_I(" %s", extension); if (std::strcmp(extension, "GL_EXT_buffer_storage") == 0) { caps.SupportsPersistentMapping = true; } if (std::strcmp(extension, "GL_EXT_texture_norm16") == 0) { caps.SupportsNorm16Texture = true; } if (std::strcmp(extension, "GL_EXT_render_snorm") == 0) { caps.SupportsRenderSnorm = true; } if (std::strcmp(extension, "GL_EXT_color_buffer_float") == 0) { caps.SupportsColorBufferFloat = true; } if (std::strcmp(extension, "GL_EXT_color_buffer_half_float") == 0) { caps.SupportsColorBufferHalfFloat = true; } if (std::strcmp(extension, "GL_EXT_sRGB_write_control") == 0) { caps.SupportsSrgbWriteControl = true; } if (std::strcmp(extension, "GL_EXT_texture_filter_anisotropic") == 0) { caps.SupportsTextureFilterAnisotropy = true; } if (std::strcmp(extension, "GL_EXT_texture_border_clamp") == 0 || std::strcmp(extension, "GL_OES_texture_border_clamp") == 0) { caps.SupportsTextureBorderClamp = true; } if (std::strcmp(extension, "GL_EXT_texture_cube_map_array") == 0 || std::strcmp(extension, "GL_OES_texture_cube_map_array") == 0) { caps.SupportsTextureCubeMapArray = true; } if (std::strcmp(extension, "GL_EXT_texture_view") == 0) { hasExtTextureView = true; } if (std::strcmp(extension, "GL_OES_texture_view") == 0) { hasOesTextureView = true; } if (std::strcmp(extension, "GL_EXT_texture_buffer") == 0) { hasExtTextureBuffer = true; } if (std::strcmp(extension, "GL_OES_texture_buffer") == 0) { hasOesTextureBuffer = true; } if (std::strcmp(extension, "GL_EXT_base_instance") == 0) { hasBaseInstanceExtension = true; } if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) { caps.SupportsDisjointTimerQuery = true; } if (std::strcmp(extension, "GL_KHR_parallel_shader_compile") == 0) { caps.SupportsParallelShaderCompile = true; } if (std::strcmp(extension, "GL_EXT_blend_func_extended") == 0) { caps.SupportsDualSourceBlend = true; } if (std::strcmp(extension, "GL_NV_shader_noperspective_interpolation") == 0) { caps.SupportsNoperspectiveInterpolation = true; } if (std::strcmp(extension, "GL_NV_image_formats") == 0) { caps.SupportsExtendedImageFormats = true; } if (std::strcmp(extension, "GL_OES_shader_multisample_interpolation") == 0) { caps.SupportsShaderMultisampleInterpolation = true; } if (std::strcmp(extension, "GL_EXT_multi_draw_indirect") == 0) { hasMultiDrawIndirectExtension = true; } if (std::strcmp(extension, "GL_EXT_draw_elements_base_vertex") == 0 || std::strcmp(extension, "GL_OES_draw_elements_base_vertex") == 0) { hasDrawElementsBaseVertexExtension = true; } if (std::strcmp(extension, "GL_EXT_multi_draw_arrays") == 0) { hasMultiDrawArraysExtension = true; } if (std::strcmp(extension, "GL_EXT_clip_cull_distance") == 0) { caps.SupportsClipDistance = true; } if (std::strcmp(extension, "GL_OES_viewport_array") == 0) { caps.SupportsViewportArray = true; } } } // The pointer check on top of the extension check makes each flag sufficient on its own // at a call site; the extension check on top of the pointer keeps a stub returned by // eglGetProcAddress (see AcquireGLESFunctions) from ever counting as support. caps.SupportsMultiDrawIndirect = hasMultiDrawIndirectExtension && glesFuncs.glMultiDrawArraysIndirectEXT != nullptr && glesFuncs.glMultiDrawElementsIndirectEXT != nullptr; caps.SupportsMultiDrawElementsBaseVertex = hasDrawElementsBaseVertexExtension && hasMultiDrawArraysExtension && glesFuncs.glMultiDrawElementsBaseVertexEXT != nullptr; // All three, not any: DirectGLES picks native-vs-emulated once per draw entry point off // this single flag, so a driver that resolved only some of them must count as absent. caps.SupportsBaseInstance = hasBaseInstanceExtension && glesFuncs.glDrawArraysInstancedBaseInstanceEXT != nullptr && glesFuncs.glDrawElementsInstancedBaseInstanceEXT != nullptr && glesFuncs.glDrawElementsInstancedBaseVertexBaseInstanceEXT != nullptr; // ES has no core texture views at any version, so this is extension-only by nature. // Each spelling must bring its OWN entry point: a driver that advertises the OES string // is not required to export glTextureViewEXT. caps.SupportsTextureView = (hasExtTextureView && glesFuncs.glTextureViewEXT != nullptr) || (hasOesTextureView && glesFuncs.glTextureViewOES != nullptr); // Escape hatch for the integration suite: the no-extension path is the one MobileGL has // to refuse honestly rather than emulate, and on a driver that HAS the extension there // would otherwise be no way to exercise that refusal (see TextureViewScenario). if (std::getenv("MOBILEGL_DISABLE_TEXTURE_VIEW") != nullptr) { MGLOG_I("MOBILEGL_DISABLE_TEXTURE_VIEW is set; reporting no EXT/OES_texture_view support"); caps.SupportsTextureView = false; } // Core from ES 3.2 on, so an extension string is not required there; below 3.2 the // extension is, and the pointer still has to have resolved either way. const Bool esAtLeast32 = caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); const Bool esAtLeast31 = caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); caps.SupportsDrawIndirect = esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr && glesFuncs.glDrawElementsIndirect != nullptr; caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) && glesFuncs.glDrawElementsBaseVertex != nullptr; caps.SupportsComputeShader = esAtLeast31 && glesFuncs.glDispatchCompute != nullptr && glesFuncs.glMemoryBarrier != nullptr && glesFuncs.glCreateShader != nullptr && glesFuncs.glCreateProgram != nullptr; caps.SupportsShaderMultisampleInterpolation = caps.SupportsShaderMultisampleInterpolation || caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); // Detect optional raster/color-mask entry points by whether they loaded. glColorMaski is GLES // 3.2 core (no extension string), so pointer presence is the reliable signal for all of these. caps.SupportsPolygonMode = glesFuncs.glPolygonModeNV != nullptr || glesFuncs.glPolygonModeANGLE != nullptr; caps.SupportsIndexedColorMask = glesFuncs.glColorMaski != nullptr || glesFuncs.glColorMaskiEXT != nullptr || glesFuncs.glColorMaskiOES != nullptr; MGLOG_I(" glPolygonMode (NV/ANGLE): %s", caps.SupportsPolygonMode ? "yes" : "no"); MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no"); MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s", caps.SupportsDualSourceBlend ? "yes" : "no"); MGLOG_I(" draw indirect (ES 3.1 core): %s", caps.SupportsDrawIndirect ? "yes" : "no"); MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s", caps.SupportsMultiDrawIndirect ? "yes" : "no"); MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s", caps.SupportsMultiDrawElementsBaseVertex ? "yes" : "no"); MGLOG_I(" draw elements base vertex (ES 3.2 core or EXT/OES_draw_elements_base_vertex): %s", caps.SupportsDrawElementsBaseVertex ? "yes" : "no"); MGLOG_I(" compute shaders (ES 3.1 core): %s", caps.SupportsComputeShader ? "yes" : "no"); MGLOG_I(" base instance (EXT_base_instance; emulated by attribute offsets when absent): %s", caps.SupportsBaseInstance ? "yes" : "no"); MGLOG_I(" clip distances (EXT_clip_cull_distance): %s", caps.SupportsClipDistance ? "yes" : "no"); MGLOG_I(" viewport array (OES_viewport_array; gl_ViewportIndex collapses to viewport 0 when absent): %s", caps.SupportsViewportArray ? "yes" : "no"); // LOAD-BEARING STRING, not just a banner. android-plugin/trace-replay-ci.sh's // is_angle_surface_lost() greps mobilegl.log for exactly "OpenGL ES capabilities:" to // decide whether MobileGL got far enough to have a working context: if the probe ran, // a later surface loss is a real defect rather than an emulator fault worth retrying. // Demoting this line, renaming it, or moving it before the context is usable silently // inverts that retry logic. It is init-phase, so MGLOG_I is correct and it stays. MGLOG_I("OpenGL ES capabilities:"); glesFuncs.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &caps.UniformBufferOffsetAlignment); MGLOG_I(" GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: %d", caps.UniformBufferOffsetAlignment); GLfloat aliasedLineWidthRange[2] = {1.0f, 1.0f}; GLfloat smoothLineWidthRange[2] = {1.0f, 1.0f}; GLfloat smoothLineWidthGranularity = 1.0f; GLfloat aliasedPointSizeRange[2] = {1.0f, 1.0f}; // GL 4.6 core table 23.60 sets the MINIMUM VIEWPORT_BOUNDS_RANGE at [-32768, 32767], and // KHR-GL43.viewport_array.queries asserts exactly that floor. GLES has no such query, so // the glGetFloatv below raises GL_INVALID_ENUM and leaves this untouched - starting it at // {0, 0} advertised a range that admits no viewport origin at all. GLfloat viewportBoundsRange[2] = {-32768.0f, 32767.0f}; GLint maxViewportDims[2] = {16384, 16384}; GLint viewportSubpixelBits = 0; GLint max3DTextureSize = 16384; GLint maxArrayTextureLayers = 2048; GLint maxCubeMapTextureSize = 16384; GLint maxFramebufferWidth = 16384; GLint maxFramebufferHeight = 16384; GLint maxFramebufferLayers = 2048; GLint maxRenderbufferSize = 16384; GLint maxTextureSize = 16384; GLint maxColorTextureSamples = 1; GLint maxDepthTextureSamples = 1; GLint maxFramebufferSamples = 1; GLint maxIntegerSamples = 1; GLint maxSamples = 1; GLint maxSampleMaskWords = 1; GLint maxTextureImageUnits = 32; GLint maxVertexTextureImageUnits = 32; GLint maxComputeTextureImageUnits = 32; GLint maxCombinedTextureImageUnits = 192; GLint maxVertexAttribs = 16; GLint maxComputeShaderStorageBlocks = 8; GLint maxCombinedShaderStorageBlocks = 32; // ES 3.2 table 21.44 minimums. Zero for the four graphics stages below fragment is not a // placeholder - it is what the spec permits and what ARM's GLES driver actually reports, // so a probe that never runs (pre-ES 3.2, unsupported pname) leaves behind the truthful // answer rather than an optimistic one. GLint maxVertexShaderStorageBlocks = 0; GLint maxTessControlShaderStorageBlocks = 0; GLint maxTessEvaluationShaderStorageBlocks = 0; GLint maxGeometryShaderStorageBlocks = 0; GLint maxFragmentShaderStorageBlocks = 4; GLint maxComputeUniformBlocks = 12; GLint maxComputeWorkGroupInvocations = 128; GLint maxShaderStorageBufferBindings = 8; GLint maxTextureBufferSize = 65536; GLint maxUniformBufferBindings = 24; GLint maxUniformBlockSize = 16384; GLint maxImageUnits = 8; GLint maxCombinedImageUniforms = 8; GLint maxVertexImageUniforms = 0; GLint maxGeometryImageUniforms = 0; GLint maxFragmentImageUniforms = 8; GLint maxComputeImageUniforms = 8; GLint maxDrawBuffers = 8; GLint maxColorAttachments = 8; // Zero is a legal answer, not a placeholder. GL_MAX_CLIP_DISTANCES exists in ES only as // GL_MAX_CLIP_DISTANCES_EXT under GL_EXT_clip_cull_distance, so on a driver without that // extension there is nowhere to put a clip distance at all: SPIRV-Cross emits // gl_ClipDistance behind an `#extension ... : require` the ESSL compiler rejects, and // DirectGLES has no state to forward the per-distance enables into (see the gate in // DirectGLES::SyncRenderState). Starting at 8 meant a probe that could never run left an // optimistic 8 behind, so the frontend promised eight clip planes and every draw with a // clipping program silently rendered nothing. The guarded probe below only ever widens it. GLint maxClipDistances = 0; GLint maxViewports = 16; // GL_UNDEFINED_VERTEX is what stands when the probes below cannot run, and it is a legal // answer rather than a placeholder: with neither geometry shaders nor a viewport array // there is no layered or multi-viewport draw for a convention to describe. GLenum layerProvokingVertex = GL_UNDEFINED_VERTEX; GLenum viewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; GLfloat minFragmentInterpolationOffset = -0.5f; GLfloat maxFragmentInterpolationOffset = 0.4375f; GLint fragmentInterpolationOffsetBits = 4; // Core minimums of both APIs (GL 4.6 table 23.53, ES 3.1 table 20.40); the probe // below only ever widens them. GLint minProgramTextureGatherOffset = -8; GLint maxProgramTextureGatherOffset = 7; GLint maxPatchVertices = 32; GLint maxTessGenLevel = 64; // Function-scope, and used by every probe group below rather than redeclared inside each // one. Returns whether anything was drained, which is what lets a group tell "the driver // answered" from "the driver rejected the pname and left my local alone". const auto drainErrors = [&glesFuncs]() { Bool hadError = false; if (glesFuncs.glGetError) { while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true; } return hadError; }; // THE GENERATOR OF THIS WHOLE BUG FAMILY, closed here. A bare glGetIntegerv/glGetFloatv // of a pname the driver does not have does two damaging things at once: it leaves the // local at whatever the declaration initialised it to - an optimistic number the frontend // then advertises as a capability - and it leaves a GL_INVALID_ENUM in the queue where // the next unrelated probe's caller, or the application's first glGetError, gets blamed // for it. The per-stage storage block, fragment interpolation and buffer texture probes // below already drain and fall back; this unconditional run did neither, which is how // GL_MAX_CLIP_DISTANCES came to be advertised as 8 on a driver with no clip distances at // all. Every pname here that is not ES core is now either gated on the capability that // makes it exist or floored at the value a rejected probe would have left, and the whole // run is bracketed by a drain. drainErrors(); glesFuncs.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange); // GL_SMOOTH_LINE_WIDTH_RANGE / GL_SMOOTH_LINE_WIDTH_GRANULARITY (0x0B22 / 0x0B23) are // desktop-only - ES has never had an antialiased line width query - so on a real GLES // driver these two raise GL_INVALID_ENUM. Kept as probes rather than dropped because the // ANGLE and desktop-GL hosts MobileGL also runs on do answer them; the initialisers are // the GL 4.6 table 23.55 minimum of [1, 1], which is both the honest answer for a driver // that cannot say and what an untouched out-param already holds. glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange); glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &smoothLineWidthGranularity); glesFuncs.glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, aliasedPointSizeRange); glesFuncs.glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DTextureSize); glesFuncs.glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArrayTextureLayers); glesFuncs.glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxCubeMapTextureSize); glesFuncs.glGetIntegerv(GL_MAX_FRAMEBUFFER_WIDTH, &maxFramebufferWidth); glesFuncs.glGetIntegerv(GL_MAX_FRAMEBUFFER_HEIGHT, &maxFramebufferHeight); glesFuncs.glGetIntegerv(GL_MAX_FRAMEBUFFER_LAYERS, &maxFramebufferLayers); glesFuncs.glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize); glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); glesFuncs.glGetIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxColorTextureSamples); glesFuncs.glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &maxDepthTextureSamples); glesFuncs.glGetIntegerv(GL_MAX_FRAMEBUFFER_SAMPLES, &maxFramebufferSamples); glesFuncs.glGetIntegerv(GL_MAX_INTEGER_SAMPLES, &maxIntegerSamples); glesFuncs.glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); glesFuncs.glGetIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords); // MobileGL's sample-mask state only stores a single 32-bit word (see // RenderState::SampleMaskValue) and SampleMaski_State() rejects any // maskNumber other than 0. Advertising the real driver's value here (e.g. // NVIDIA's GLES driver reports 2) makes glSampleMaski(1, ...) - which // dEQP's per-case gluStateReset always issues up to GL_MAX_SAMPLE_MASK_WORDS - // raise GL_INVALID_VALUE and aborts the whole glcts process after every // single test case. 1 is a spec-legal value (the minimum required), so cap // to what is actually implemented instead of forwarding the raw driver limit. maxSampleMaskWords = std::min(maxSampleMaskWords, 1); // The multisample ceilings above are ES 3.1 state apart from GL_MAX_SAMPLES, which is ES // 3.0, so a 3.0 context rejects five of the six and leaves whatever the out-param held. // One sample is what a rejected probe leaves behind and is also the smallest legal // answer, so clamp rather than trust: a zero reaching GL_Getter would have the frontend // reject the very sample count it just advertised (see GetAdvertisedMaxSamples). maxColorTextureSamples = std::max(maxColorTextureSamples, 1); maxDepthTextureSamples = std::max(maxDepthTextureSamples, 1); maxFramebufferSamples = std::max(maxFramebufferSamples, 1); maxIntegerSamples = std::max(maxIntegerSamples, 1); maxSamples = std::max(maxSamples, 1); maxSampleMaskWords = std::max(maxSampleMaskWords, 1); // ES 3.2 core, or EXT_tessellation_shader on 3.1. Probed rather than version-gated so a // 3.1 driver that HAS the extension still gets to answer; the clamp below is what makes a // rejected query safe, since GL 4.6 table 23.66 and ES 3.2 table 21.45 set the same // minimums the initialisers carry and neither API permits less. glesFuncs.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices); glesFuncs.glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel); maxPatchVertices = std::max(maxPatchVertices, 32); maxTessGenLevel = std::max(maxTessGenLevel, 64); glesFuncs.glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET, &minProgramTextureGatherOffset); glesFuncs.glGetIntegerv(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET, &maxProgramTextureGatherOffset); // A driver that leaves the probe untouched (pre-ES 3.1, or an ignored enum) must not // drag the advertised range below what GL 4.0 requires of us. minProgramTextureGatherOffset = std::min(minProgramTextureGatherOffset, -8); maxProgramTextureGatherOffset = std::max(maxProgramTextureGatherOffset, 7); glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits); glesFuncs.glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &maxComputeTextureImageUnits); glesFuncs.glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxCombinedTextureImageUnits); glesFuncs.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &maxComputeShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, &maxCombinedShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_UNIFORM_BLOCKS, &maxComputeUniformBlocks); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &maxComputeWorkGroupInvocations); glesFuncs.glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings); // GL_MAX_TEXTURE_BUFFER_SIZE is deliberately NOT batched here: like // GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT below, the pname only exists once buffer textures do, // so on a driver without them it raises GL_INVALID_ENUM, leaves the local at MobileGL's own // floor, and - because nothing drains the queue until the alignment probe far below - lets // that error be misattributed to any query in between. It is queried in the guarded block // that resolves caps.TextureBufferSupport instead. glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings); glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize); glesFuncs.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); glesFuncs.glGetIntegerv(GL_MAX_COMBINED_IMAGE_UNIFORMS, &maxCombinedImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &maxVertexImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); // Geometry shaders and their image-uniform query are core only in ES 3.2. DirectGLES // emits ESSL 3.10 on an ES 3.1 context, so reporting zero there is both legal and an // accurate description of what the backend compiler can consume. if (caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) { glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &maxGeometryImageUniforms); } // Closes the bracket opened before the run: every local above now holds either the // driver's answer or a floor, and nothing this function asked for is left in the error // queue for a later probe - or the application - to be blamed for. if (drainErrors()) { MGLOG_W("One or more capability queries were rejected by this driver; the affected " "limits keep MobileGL's spec-minimum floors"); } // Per-stage storage-block counts. Deliberately NOT batched with the unconditional probes // above, for the reason GL_MAX_TEXTURE_BUFFER_SIZE is not: the vertex and fragment pnames // are ES 3.1, but the tessellation and geometry ones only exist from ES 3.2 on (or under // EXT_tessellation_shader / EXT_geometry_shader), so on an older context they raise // GL_INVALID_ENUM, leave the local untouched, and - with nothing draining the queue until // some later probe - let that error be misattributed to an unrelated query in between, or // leak into the application's first glGetError. // // A stage whose probe does not run keeps the spec minimum, which for all four graphics // stages is 0. That is the honest answer: DirectGLES emits ESSL 3.10 on an ES 3.1 context, // where those stages do not exist at all. { // Isolate from errors raised by the preceding probes so the drain below reports on // these queries only. drainErrors(); glesFuncs.glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks); if (drainErrors()) { MGLOG_W("Per-stage shader storage block query failed for the vertex/fragment " "stages; assuming the ES minimums (vertex 0, fragment 4)"); maxVertexShaderStorageBlocks = 0; maxFragmentShaderStorageBlocks = 4; } if (esAtLeast32) { glesFuncs.glGetIntegerv(GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, &maxTessControlShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, &maxTessEvaluationShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS, &maxGeometryShaderStorageBlocks); if (drainErrors()) { MGLOG_W("Per-stage shader storage block query failed for the tessellation/" "geometry stages; assuming the ES minimum of 0"); maxTessControlShaderStorageBlocks = 0; maxTessEvaluationShaderStorageBlocks = 0; maxGeometryShaderStorageBlocks = 0; } } // A driver is free to report a negative or nonsensical count into an untouched // out-param; clamp before anything downstream treats it as a capacity. maxVertexShaderStorageBlocks = std::max(maxVertexShaderStorageBlocks, 0); maxTessControlShaderStorageBlocks = std::max(maxTessControlShaderStorageBlocks, 0); maxTessEvaluationShaderStorageBlocks = std::max(maxTessEvaluationShaderStorageBlocks, 0); maxGeometryShaderStorageBlocks = std::max(maxGeometryShaderStorageBlocks, 0); maxFragmentShaderStorageBlocks = std::max(maxFragmentShaderStorageBlocks, 0); } glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments); // GL_MAX_CLIP_DISTANCES is 0x0D32, which ES only ever spells GL_MAX_CLIP_DISTANCES_EXT and // only ever has under GL_EXT_clip_cull_distance. The extension was already resolved into // caps.SupportsClipDistance a few hundred lines above and is the same flag DirectGLES // gates the CLIP_DISTANCEi enable forwarding on, so ask the driver only where the pname // exists; everywhere else the honest 0 stands and no GL_INVALID_ENUM is left behind for an // unrelated query - or the application's first glGetError - to trip over. if (caps.SupportsClipDistance) { drainErrors(); glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances); if (drainErrors()) { MGLOG_W("GL_EXT_clip_cull_distance is advertised but GL_MAX_CLIP_DISTANCES was " "rejected; reporting no clip distances"); maxClipDistances = 0; } } glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims); // GL_LAYER_PROVOKING_VERTEX is ES 3.2 core (it arrives with geometry shaders, which is // what gl_Layer needs). Ask the driver where the pname exists rather than asserting a // convention: it is a statement about which vertex of a primitive supplies gl_Layer, and // MobileGL forwards the geometry stage to the driver rather than implementing the // selection itself, so the driver's answer IS MobileGL's answer. Below ES 3.2 there are // no layered draws to have a convention for and GL_UNDEFINED_VERTEX stands, which GL 4.6 // table 23.65 explicitly permits. if (esAtLeast32) { GLint driverLayerConvention = static_cast(GL_UNDEFINED_VERTEX); drainErrors(); glesFuncs.glGetIntegerv(GL_LAYER_PROVOKING_VERTEX, &driverLayerConvention); if (!drainErrors()) { layerProvokingVertex = NormalizeProvokingVertexConvention(driverLayerConvention); } } // GL_MAX_VIEWPORTS (0x825B), GL_VIEWPORT_SUBPIXEL_BITS (0x825C) and GL_VIEWPORT_BOUNDS_RANGE // (0x825D) all arrive with GL_OES_viewport_array and exist nowhere in ES core, so on the // drivers DirectGLES actually runs on all three raise GL_INVALID_ENUM. The values MobileGL // advertises do not change by asking: GL_Getter answers GL_MAX_VIEWPORTS from the frontend // state width (indexed viewport entry points validate against RenderStateParameters:: // MAX_VIEWPORTS, so a device answer of 1 would reject indices the state can legitimately // hold), floors GL_SUBPIXEL_BITS at its own 4, and the bounds range is clamped to the core // minimum below. What changes is that the errors stop being manufactured. if (caps.SupportsViewportArray) { GLint driverViewportIndexConvention = static_cast(GL_UNDEFINED_VERTEX); drainErrors(); glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports); glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits); glesFuncs.glGetIntegerv(GL_VIEWPORT_INDEX_PROVOKING_VERTEX, &driverViewportIndexConvention); if (glesFuncs.glGetFloatv) { glesFuncs.glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE, viewportBoundsRange); } if (drainErrors()) { MGLOG_W("GL_OES_viewport_array is advertised but its viewport limit queries were " "rejected; keeping the OpenGL core minimums"); maxViewports = 16; viewportSubpixelBits = 0; viewportBoundsRange[0] = -32768.0f; viewportBoundsRange[1] = 32767.0f; } else { viewportIndexProvokingVertex = NormalizeProvokingVertexConvention(driverViewportIndexConvention); } } if (caps.SupportsShaderMultisampleInterpolation && glesFuncs.glGetFloatv) { // Isolate these optional queries from errors raised by preceding capability // probes, then consume any query error so initialization never leaks it into // the application's first glGetError call. drainErrors(); glesFuncs.glGetFloatv(GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, &minFragmentInterpolationOffset); glesFuncs.glGetFloatv(GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, &maxFragmentInterpolationOffset); glesFuncs.glGetIntegerv(GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, &fragmentInterpolationOffsetBits); if (drainErrors()) { MGLOG_W("Fragment interpolation limit query failed; using OpenGL minimums"); minFragmentInterpolationOffset = -0.5f; maxFragmentInterpolationOffset = 0.4375f; fragmentInterpolationOffsetBits = 4; } } // Only legal to query once the extension has been seen in the loop above, hence not batched // with the unconditional probes: on a driver without it this raises GL_INVALID_ENUM. // Core from ES 3.2 on, whatever the extension string says. if (caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) { caps.SupportsTextureBorderClamp = true; caps.SupportsTextureCubeMapArray = true; } // Buffer-texture tier. Core from ES 3.2 on; below that the EXT spelling is preferred over // the OES one purely because SPIRV-Cross emits GL_EXT_texture_buffer natively, so a driver // with both needs no directive retargeting. The entry point has to have resolved either // way - the extension string alone is not support (see the multi-draw note above). { using Tier = MG_External::GLESCapabilities::TextureBufferTier; // Each tier needs the entry point that tier's support actually ships. Gating all // three on the unsuffixed name - the ES 3.2 CORE spelling - would make every // EXT/OES driver look unsupported on a strict loader, and would make MobileGL call // a core entry point the driver never exported on a permissive one. The suffixed // name is preferred where the support is an extension, with the core name accepted // as a fallback because drivers that expose both alias them. const Bool hasCoreEntryPoint = glesFuncs.glTexBuffer != nullptr; if (esAtLeast32 && hasCoreEntryPoint) { caps.TextureBufferSupport = Tier::CoreEs32; } else if (hasExtTextureBuffer && (glesFuncs.glTexBufferEXT != nullptr || hasCoreEntryPoint)) { caps.TextureBufferSupport = Tier::ExtensionEXT; } else if (hasOesTextureBuffer && (glesFuncs.glTexBufferOES != nullptr || hasCoreEntryPoint)) { caps.TextureBufferSupport = Tier::ExtensionOES; } else { caps.TextureBufferSupport = Tier::None; } // Assigned unconditionally, like every other capability in this function, so a // second fill on a reused struct cannot keep a stale true. caps.MaxTextureBufferSizeIsDriverReported = false; if (caps.TextureBufferSupport != Tier::None) { // Drain first: an error left by any earlier probe would otherwise read as this // query having failed, and the value would be discarded as a non-answer. if (glesFuncs.glGetError) { while (glesFuncs.glGetError() != GL_NO_ERROR) { } } glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize); if (glesFuncs.glGetError) { Bool queryFailed = false; while (glesFuncs.glGetError() != GL_NO_ERROR) { queryFailed = true; } caps.MaxTextureBufferSizeIsDriverReported = !queryFailed; } else { caps.MaxTextureBufferSizeIsDriverReported = true; } } // On the None tier the local keeps MobileGL's floor and // MaxTextureBufferSizeIsDriverReported stays false. The floor, not 0, is what the // frontend goes on advertising: MobileGL reports an OpenGL 4.x context, where buffer // textures are core and GL_MAX_TEXTURE_BUFFER_SIZE has a spec minimum of 65536, so 0 // would be an illegal answer that no conformant app is prepared to read (several // divide by it or size an allocation with it). The dishonesty is contained by making // the missing capability loud instead - at capability init here, at glTexBuffer, at // program build, and as its own driver POST row - because GL offers no way to say // "buffer textures exist but cannot work". if (caps.TextureBufferSupport == Tier::None) { // Two ways to land here, and they are worth telling apart: the ordinary one (too // old, no extension) and the pathological one (the driver says it has them but // no entry point resolved), which is a driver or loader fault, not a missing // feature. const Bool claimsSupport = esAtLeast32 || hasExtTextureBuffer || hasOesTextureBuffer; MGLOG_I(" Buffer textures: UNSUPPORTED (%s). Any shader sampling a " "samplerBuffer will fail to compile, and MobileGL keeps advertising " "GL_MAX_TEXTURE_BUFFER_SIZE = %d because a GL 4.x context may not report 0.", claimsSupport ? "this driver advertises buffer textures but no glTexBuffer entry " "point resolved, so none of them can be called" : "ES core needs 3.2, and neither GL_EXT_texture_buffer nor " "GL_OES_texture_buffer is present", maxTextureBufferSize); } } if (caps.SupportsTextureFilterAnisotropy) { GLfloat maxTextureMaxAnisotropy = 1.0f; glesFuncs.glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureMaxAnisotropy); caps.MaxTextureMaxAnisotropy = std::max(maxTextureMaxAnisotropy, 1.0f); } caps.AliasedLineWidthRangeMin = aliasedLineWidthRange[0]; caps.AliasedLineWidthRangeMax = aliasedLineWidthRange[1]; caps.SmoothLineWidthRangeMin = smoothLineWidthRange[0]; caps.SmoothLineWidthRangeMax = smoothLineWidthRange[1]; caps.SmoothLineWidthGranularity = smoothLineWidthGranularity; caps.PointSizeRangeMin = aliasedPointSizeRange[0]; caps.PointSizeRangeMax = aliasedPointSizeRange[1]; caps.PointSizeGranularity = 1.0f; caps.Max3DTextureSize = max3DTextureSize; caps.MaxArrayTextureLayers = maxArrayTextureLayers; caps.MaxCubeMapTextureSize = maxCubeMapTextureSize; caps.MaxFramebufferWidth = maxFramebufferWidth; caps.MaxFramebufferHeight = maxFramebufferHeight; caps.MaxFramebufferLayers = maxFramebufferLayers; caps.MaxRenderbufferSize = maxRenderbufferSize; caps.MaxTextureSize = maxTextureSize; caps.MaxColorTextureSamples = maxColorTextureSamples; caps.MaxDepthTextureSamples = maxDepthTextureSamples; caps.MaxFramebufferSamples = maxFramebufferSamples; caps.MaxIntegerSamples = maxIntegerSamples; caps.MaxSamples = maxSamples; caps.MaxSampleMaskWords = maxSampleMaskWords; caps.MaxPatchVertices = maxPatchVertices; caps.MaxTessGenLevel = maxTessGenLevel; caps.MinProgramTextureGatherOffset = minProgramTextureGatherOffset; caps.MaxProgramTextureGatherOffset = maxProgramTextureGatherOffset; caps.MaxTextureImageUnits = maxTextureImageUnits; caps.MaxVertexTextureImageUnits = maxVertexTextureImageUnits; caps.MaxComputeTextureImageUnits = maxComputeTextureImageUnits; caps.MaxCombinedTextureImageUnits = maxCombinedTextureImageUnits; caps.MaxVertexAttribs = maxVertexAttribs; caps.MaxComputeShaderStorageBlocks = maxComputeShaderStorageBlocks; caps.MaxCombinedShaderStorageBlocks = maxCombinedShaderStorageBlocks; caps.MaxVertexShaderStorageBlocks = maxVertexShaderStorageBlocks; caps.MaxTessControlShaderStorageBlocks = maxTessControlShaderStorageBlocks; caps.MaxTessEvaluationShaderStorageBlocks = maxTessEvaluationShaderStorageBlocks; caps.MaxGeometryShaderStorageBlocks = maxGeometryShaderStorageBlocks; caps.MaxFragmentShaderStorageBlocks = maxFragmentShaderStorageBlocks; caps.MaxComputeUniformBlocks = maxComputeUniformBlocks; caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations; caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings; caps.MaxTextureBufferSize = maxTextureBufferSize; // Through glesFuncs, like every other capability query here: a bare glGetIntegerv resolves // to MobileGL's own exported entry point, which answers this pname from the very // capability table being filled in - so the driver's real alignment never arrived and the // backend reported an unconstrained offset it cannot honour. GLint textureBufferOffsetAlignment = 1; glesFuncs.glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment); // Core in ES 3.2 and in EXT_texture_buffer; an older context rejects the pname and leaves // the default in place. if (glesFuncs.glGetError) { while (glesFuncs.glGetError() != GL_NO_ERROR) {} } caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment); caps.MaxUniformBufferBindings = maxUniformBufferBindings; caps.MaxUniformBlockSize = maxUniformBlockSize; caps.MaxImageUnits = maxImageUnits; caps.MaxCombinedImageUniforms = maxCombinedImageUniforms; caps.MaxVertexImageUniforms = maxVertexImageUniforms; caps.MaxGeometryImageUniforms = maxGeometryImageUniforms; caps.MaxFragmentImageUniforms = maxFragmentImageUniforms; caps.MaxComputeImageUniforms = maxComputeImageUniforms; caps.MaxDrawBuffers = maxDrawBuffers; caps.MaxColorAttachments = maxColorAttachments; // A driver is free to write nonsense into an out-param it then rejects, and without the // extension the probe above never ran at all - so the flag, not the local, decides. caps.MaxClipDistances = caps.SupportsClipDistance ? std::max(maxClipDistances, 0) : 0; caps.MaxViewports = maxViewports; caps.LayerProvokingVertex = layerProvokingVertex; caps.ViewportIndexProvokingVertex = viewportIndexProvokingVertex; caps.MaxViewportWidth = maxViewportDims[0]; caps.MaxViewportHeight = maxViewportDims[1]; // Only ever WIDER than the core minimum: a driver that answered the query is allowed to // exceed the floor but never to sit inside it, and a driver that rejected the query left // the floor in place. Written as a clamp rather than a plain assignment so a partial // write (one component answered, the other not) cannot narrow the range either. caps.ViewportBoundsRangeMin = std::min(viewportBoundsRange[0], -32768.0f); caps.ViewportBoundsRangeMax = std::max(viewportBoundsRange[1], 32767.0f); caps.ViewportSubpixelBits = viewportSubpixelBits; caps.MinFragmentInterpolationOffset = std::isfinite(minFragmentInterpolationOffset) && minFragmentInterpolationOffset <= -0.5f ? minFragmentInterpolationOffset : -0.5f; caps.MaxFragmentInterpolationOffset = 0.4375f; caps.FragmentInterpolationOffsetBits = 4; if (fragmentInterpolationOffsetBits >= 4 && std::isfinite(maxFragmentInterpolationOffset)) { const Float requiredMaxOffset = 0.5f - std::ldexp(1.0f, -fragmentInterpolationOffsetBits); if (maxFragmentInterpolationOffset >= requiredMaxOffset) { caps.MaxFragmentInterpolationOffset = maxFragmentInterpolationOffset; caps.FragmentInterpolationOffsetBits = fragmentInterpolationOffsetBits; } } MGLOG_I(" GL_ALIASED_LINE_WIDTH_RANGE: [%.3f, %.3f]", caps.AliasedLineWidthRangeMin, caps.AliasedLineWidthRangeMax); MGLOG_I(" GL_SMOOTH_LINE_WIDTH_RANGE: [%.3f, %.3f]", caps.SmoothLineWidthRangeMin, caps.SmoothLineWidthRangeMax); MGLOG_I(" GL_SMOOTH_LINE_WIDTH_GRANULARITY: %.3f", caps.SmoothLineWidthGranularity); MGLOG_I(" GL_ALIASED_POINT_SIZE_RANGE: [%.3f, %.3f]", caps.PointSizeRangeMin, caps.PointSizeRangeMax); MGLOG_I(" GL_MAX_3D_TEXTURE_SIZE: %d", caps.Max3DTextureSize); MGLOG_I(" GL_MAX_ARRAY_TEXTURE_LAYERS: %d", caps.MaxArrayTextureLayers); MGLOG_I(" GL_MAX_CUBE_MAP_TEXTURE_SIZE: %d", caps.MaxCubeMapTextureSize); MGLOG_I(" GL_MAX_FRAMEBUFFER_WIDTH: %d", caps.MaxFramebufferWidth); MGLOG_I(" GL_MAX_FRAMEBUFFER_HEIGHT: %d", caps.MaxFramebufferHeight); MGLOG_I(" GL_MAX_FRAMEBUFFER_LAYERS: %d", caps.MaxFramebufferLayers); MGLOG_I(" GL_MAX_RENDERBUFFER_SIZE: %d", caps.MaxRenderbufferSize); MGLOG_I(" GL_MAX_TEXTURE_SIZE: %d", caps.MaxTextureSize); MGLOG_I(" GL_MAX_COLOR_TEXTURE_SAMPLES: %d", caps.MaxColorTextureSamples); MGLOG_I(" GL_MAX_DEPTH_TEXTURE_SAMPLES: %d", caps.MaxDepthTextureSamples); MGLOG_I(" GL_MAX_FRAMEBUFFER_SAMPLES: %d", caps.MaxFramebufferSamples); MGLOG_I(" GL_MAX_INTEGER_SAMPLES: %d", caps.MaxIntegerSamples); MGLOG_I(" GL_MAX_SAMPLES: %d", caps.MaxSamples); MGLOG_I(" GL_MAX_SAMPLE_MASK_WORDS: %d", caps.MaxSampleMaskWords); MGLOG_I(" GL_MAX_TEXTURE_IMAGE_UNITS: %d", caps.MaxTextureImageUnits); MGLOG_I(" GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: %d", caps.MaxVertexTextureImageUnits); MGLOG_I(" GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS: %d", caps.MaxComputeTextureImageUnits); MGLOG_I(" GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d", caps.MaxCombinedTextureImageUnits); MGLOG_I(" GL_MAX_VERTEX_ATTRIBS: %d", caps.MaxVertexAttribs); MGLOG_I(" GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS: %d", caps.MaxComputeShaderStorageBlocks); MGLOG_I(" GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS: %d", caps.MaxCombinedShaderStorageBlocks); // Worth a line each: a zero here is what stops an application's storage block from ever // working in that stage, and reading it back from an artifact is the difference between // "MobileGL dropped my draw" and "this driver has no SSBOs outside compute". MGLOG_I(" GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: %d", caps.MaxVertexShaderStorageBlocks); MGLOG_I(" GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS: %d", caps.MaxTessControlShaderStorageBlocks); MGLOG_I(" GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS: %d", caps.MaxTessEvaluationShaderStorageBlocks); MGLOG_I(" GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS: %d", caps.MaxGeometryShaderStorageBlocks); MGLOG_I(" GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS: %d", caps.MaxFragmentShaderStorageBlocks); MGLOG_I(" GL_MAX_COMPUTE_UNIFORM_BLOCKS: %d", caps.MaxComputeUniformBlocks); MGLOG_I(" GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS: %d", caps.MaxComputeWorkGroupInvocations); MGLOG_I(" GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: %d", caps.MaxShaderStorageBufferBindings); // Three distinct states, and the suffix must not conflate them: a driver answer, a floor // kept because there are no buffer textures to ask about, and a floor kept because the // driver claimed buffer textures but then refused the query (which is a driver bug worth // seeing spelled out rather than hidden behind the same wording as the honest case). MGLOG_I(" GL_MAX_TEXTURE_BUFFER_SIZE: %d%s", caps.MaxTextureBufferSize, caps.MaxTextureBufferSizeIsDriverReported ? "" : (caps.TextureBufferSupport == MG_External::GLESCapabilities::TextureBufferTier::None ? " (MobileGL floor - the driver has no buffer textures to ask)" : " (MobileGL floor - the driver claims buffer textures but rejected the query)")); MGLOG_I(" GL_MAX_UNIFORM_BUFFER_BINDINGS: %d", caps.MaxUniformBufferBindings); MGLOG_I(" GL_MAX_UNIFORM_BLOCK_SIZE: %d", caps.MaxUniformBlockSize); MGLOG_I(" GL_MAX_IMAGE_UNITS: %d", caps.MaxImageUnits); MGLOG_I(" GL_MAX_COMBINED_IMAGE_UNIFORMS: %d", caps.MaxCombinedImageUniforms); MGLOG_I(" GL_MAX_VERTEX_IMAGE_UNIFORMS: %d", caps.MaxVertexImageUniforms); MGLOG_I(" GL_MAX_GEOMETRY_IMAGE_UNIFORMS: %d", caps.MaxGeometryImageUniforms); MGLOG_I(" GL_MAX_FRAGMENT_IMAGE_UNIFORMS: %d", caps.MaxFragmentImageUniforms); MGLOG_I(" GL_MAX_COMPUTE_IMAGE_UNIFORMS: %d", caps.MaxComputeImageUniforms); MGLOG_I(" GL_MAX_DRAW_BUFFERS: %d", caps.MaxDrawBuffers); MGLOG_I(" GL_MAX_COLOR_ATTACHMENTS: %d", caps.MaxColorAttachments); // Worth spelling the reason out for the same reason the per-stage storage block counts // are: a zero here is what stops an application's gl_ClipDistance from ever clipping, and // reading it back from an artifact is the difference between "MobileGL dropped my draw" // and "this driver has no clip distances". MGLOG_I(" GL_MAX_CLIP_DISTANCES: %d%s", caps.MaxClipDistances, caps.SupportsClipDistance ? "" : " (no GL_EXT_clip_cull_distance on this driver)"); MGLOG_I(" GL_MAX_VIEWPORTS: %d", caps.MaxViewports); MGLOG_I(" GL_MAX_VIEWPORT_DIMS: [%d, %d]", caps.MaxViewportWidth, caps.MaxViewportHeight); MGLOG_I(" GL_VIEWPORT_BOUNDS_RANGE: [%.3f, %.3f]", caps.ViewportBoundsRangeMin, caps.ViewportBoundsRangeMax); MGLOG_I(" GL_VIEWPORT_SUBPIXEL_BITS: %d", caps.ViewportSubpixelBits); MGLOG_I(" GL_LAYER_PROVOKING_VERTEX: %s", ProvokingVertexConventionName(caps.LayerProvokingVertex)); MGLOG_I(" GL_VIEWPORT_INDEX_PROVOKING_VERTEX: %s", ProvokingVertexConventionName(caps.ViewportIndexProvokingVertex)); caps.IndirectDrawInstanceIdIncludesBaseInstance = ProbeIndirectInstanceIdIncludesBaseInstance(caps, glesFuncs); 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.AvoidSamplerMipmapMinFilter; caps.AvoidExplicitLodBias = caps.IsAngleLlvmpipeRenderer && MG_Config::Features.AvoidExplicitLodBias; MGLOG_I(" GL_EXT_disjoint_timer_query supported: %s", caps.SupportsDisjointTimerQuery ? "true" : "false"); MGLOG_I(" GL_KHR_parallel_shader_compile supported: %s", caps.SupportsParallelShaderCompile ? "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"); MGLOG_I(" Avoid explicit LOD bias: %s", caps.AvoidExplicitLodBias ? "true" : "false"); // Last line of defence. Capability init is the very first thing that touches the driver, // so anything it leaves in the error queue surfaces at the APPLICATION's first // glGetError and gets attributed to whatever call the app happened to make. Every group // above drains its own, but a probe added later must not be able to reintroduce the leak. if (drainErrors()) { MGLOG_W("Capability initialization left a GL error behind; it has been consumed so it " "cannot surface at the application's first glGetError"); } return true; } } // namespace MobileGL::MG_Util::BackendLoader