Files
MobileGL/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
T
BZLZHH 534ec65dda [Fix] (MG_Util): ask the ES driver for the texture buffer offset alignment
The DirectGLES capability probe queried GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT with a bare
glGetIntegerv while every other query in the same function goes through glesFuncs. A bare
call resolves to MobileGL's own exported entry point, which answers that pname out of the
capability table this code is in the middle of filling in, so the value read back was the
default it started from and the driver's real alignment never arrived.

The backend therefore advertised an alignment of 1. An application that trusts that -
which is the only thing it can do - passes glTextureBufferRange an offset the ES driver
cannot honour, and the driver produces a texture that reads as zeros with no error
anywhere. The alignment llvmpipe actually wants is 16.

Takes direct_state_access.textures_buffer_* from 3 to 30 of 30 on DirectGLES, and the
whole DSA group from 66.85% to 74.12%. DirectVulkan was unaffected: its alignment comes
from a Vulkan device limit and was already right.
2026-08-05 02:26:50 +00:00

1169 lines
59 KiB
C++

// 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 <Config.h>
#include <cmath>
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
#else
#include <dlfcn.h>
#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<const void*>(&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<String>& 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<void*>(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<void*>(::GetProcAddress(reinterpret_cast<HMODULE>(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)
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)
INIT_GLES_FUNC(glMultiDrawArraysIndirectEXT)
INIT_GLES_FUNC(glMultiDrawElementsIndirectEXT)
INIT_GLES_FUNC(glMultiDrawElementsBaseVertexEXT)
}
}
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
eglLib = OpenLib({"libEGL.so"});
#endif
}
#endif // !_WIN32
if (!eglLib) {
MGLOG_E("Failed to open EGL library");
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_E("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;
}
GLint maxVertexSsboBlocks = 0;
f.glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexSsboBlocks);
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<GLint>(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;
}
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);
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_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_base_instance") == 0) {
caps.SupportsBaseInstance = true;
}
if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) {
caps.SupportsDisjointTimerQuery = 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_OES_shader_multisample_interpolation") == 0) {
caps.SupportsShaderMultisampleInterpolation = true;
}
}
}
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("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};
GLfloat viewportBoundsRange[2] = {0.0f, 0.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;
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;
GLint maxClipDistances = 8;
GLint maxViewports = 16;
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;
glesFuncs.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange);
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.glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE, viewportBoundsRange);
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);
glesFuncs.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
glesFuncs.glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel);
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);
glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize);
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);
}
glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances);
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
if (caps.SupportsShaderMultisampleInterpolation && glesFuncs.glGetFloatv) {
const auto drainErrors = [&glesFuncs]() {
Bool hadError = false;
if (glesFuncs.glGetError) {
while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true;
}
return hadError;
};
// 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.
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.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;
caps.MaxClipDistances = maxClipDistances;
caps.MaxViewports = maxViewports;
caps.MaxViewportWidth = maxViewportDims[0];
caps.MaxViewportHeight = maxViewportDims[1];
caps.ViewportBoundsRangeMin = viewportBoundsRange[0];
caps.ViewportBoundsRangeMax = viewportBoundsRange[1];
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);
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);
MGLOG_I(" GL_MAX_TEXTURE_BUFFER_SIZE: %d", caps.MaxTextureBufferSize);
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);
MGLOG_I(" GL_MAX_CLIP_DISTANCES: %d", caps.MaxClipDistances);
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);
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;
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