[Perf] (MG_Backend/DirectGLES, MG_Impl): cache link-time lookups, bound unit scans, honor eglSwapInterval

- BackendProgramObjectImpl::CacheResourceLocations resolves every
  glGetUniformBlockIndex / glGetUniformLocation string query once per
  link and establishes the block binding points there. Per draw,
  BindCurrentProgramWithResources now uses the cached indices, re-issues
  glUniform1i only when a sampler's unit actually changed (program state
  persists), uploads the global UBO only when its content version moved,
  and skips redundant glUseProgram binds (guard reset on program-name
  reuse, MakeCurrent, and every explicit glUseProgram(0)). The caches are
  invalidated through ProgramObject's link version, which also makes a
  relinked program finally re-sync its backend program.
- Track a texture-unit high-water mark (fed by glBindTexture /
  glBindTextureUnit / glBindSampler / glBindImageTexture) so the two
  per-draw unit scans (MAX_TEXTURE_IMAGE_UNITS is 192) and the
  texture-deletion unbind loop only walk units that were ever touched.
- Forward the app's eglSwapInterval to the native EGL surface through a
  new BackendObject::SetEGLSwapInterval hook (applied immediately when
  the surface exists, otherwise deferred to surface creation /
  MakeCurrent). "VSync off" finally reaches the hardware - DirectGLES
  was hard-locked to the display refresh before.

The driver-side cost of the per-draw string lookups was about half of a
30% Adreno driver hotspot; libMobileGL's share of the vanilla render
thread fell from 22% to 9% (simpleperf, Adreno 830).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 12:12:51 +00:00
co-authored by Claude Fable 5
parent bd208783d7
commit a6a5edf573
14 changed files with 255 additions and 84 deletions
+11 -1
View File
@@ -415,7 +415,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state) {
return EGL_FALSE;
}
return state->SwapInterval(dpy, interval) ? EGL_TRUE : EGL_FALSE;
if (!state->SwapInterval(dpy, interval)) {
return EGL_FALSE;
}
// Forward the request to the backend's native presentation path; without this
// the app's vsync setting only ever reaches MobileGL's shadow state and the
// native surface stays at the driver default (interval 1 = always vsynced).
auto* backendObject = GetBackendObject(state);
if (backendObject) {
backendObject->SetEGLSwapInterval(static_cast<Int>(interval));
}
return EGL_TRUE;
}
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
@@ -812,6 +812,7 @@ namespace MobileGL::MG_Impl::GLImpl {
MGLOG_D("%s: program = %d, location = %d, byteOffset = %d", __func__, programObject.GetExternalIndex(),
location, offset + byteOffsetInsideUniform);
Memcpy((char*)programObject.MapUBO() + offset + byteOffsetInsideUniform, value, ItemCount * sizeof(T));
programObject.MarkUBOContentDirty();
} else {
auto* ttype = programObject.GetUniformTType(location);
if (!ttype->isTexture() && !ttype->isImage()) return;
@@ -202,6 +202,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject((Int)unit);
MG_State::pGLContext->NoteTextureUnitTouched((Int)unit);
if (sampler == 0) {
textureUnit.SetSamplerObject(nullptr);
} else {
@@ -2495,6 +2495,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(activeUnit);
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
bindingSlot.Bind(nullptr);
MG_State::pGLContext->NoteTextureUnitTouched(activeUnit);
return;
}
@@ -2528,6 +2529,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
bindingSlot.Bind(textureObject);
MG_State::pGLContext->NoteTextureUnitTouched(MG_State::pGLContext->GetActiveTextureUnit());
}
void ActiveTexture_State(GLenum texture) {
@@ -3112,6 +3114,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(static_cast<Int>(unit));
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit));
if (texture == 0) {
for (auto& slot : textureUnit.GetAllBindingSlots()) {
slot.Bind(nullptr);
@@ -3274,6 +3277,7 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_State::pGLContext->GetImageTextureBinding(static_cast<Int>(unit))
.Bind(textureObject, level, layered, layer, access, format);
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit));
bindImageTexture(unit, texture, level, layered, layer, access, format);
}