From ad9ee995218ca8dd8262bf8deae8ce2b77d4fcdc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 12 Jul 2026 06:32:18 -0400 Subject: [PATCH] [Perf] (MG_Backend/DirectGLES): dedup per-draw indexed UBO/SSBO binds with a shadow cache; BindCurrentProgramWithResources 5.1% -> 3.1% --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 28 +++++++------ MobileGL/MG_Backend/DirectGLES/Managers.cpp | 41 +++++++++++++++++++ MobileGL/MG_Backend/DirectGLES/Managers.h | 7 ++++ 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 3dbf0788..5a2c0259 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -208,7 +208,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto& point = MG_State::pGLContext->GetBufferBindingPoint(target, i); auto& obj = point.GetBoundObject(); if (!obj) { - g_GLESFuncs.glBindBufferBase(glTarget, static_cast(i), 0); + BindBufferBaseCached(glTarget, static_cast(i), 0); continue; } @@ -222,12 +222,12 @@ namespace MobileGL::MG_Backend::DirectGLES { const auto& range = point.GetRange(); auto backendBufferId = backendResource->id; if (range.start == 0 && range.end >= obj->GetSize()) { - g_GLESFuncs.glBindBufferBase(glTarget, static_cast(i), backendBufferId); + BindBufferBaseCached(glTarget, static_cast(i), backendBufferId); } else { const auto start = std::min(range.start, obj->GetSize()); const auto end = std::min(range.end, obj->GetSize()); - g_GLESFuncs.glBindBufferRange(glTarget, static_cast(i), backendBufferId, - static_cast(start), static_cast(end - start)); + BindBufferRangeCached(glTarget, static_cast(i), backendBufferId, + static_cast(start), static_cast(end - start)); } } } @@ -1083,7 +1083,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, 0); backendProgram.SetLastUploadedGlobalUboVersion(uboContentVersion); } - g_GLESFuncs.glBindBufferBase(GL_UNIFORM_BUFFER, 0, backendProgram.GetBackendGlobalUBOId()); + BufferImpl::BindBufferBaseCached(GL_UNIFORM_BUFFER, 0, backendProgram.GetBackendGlobalUBOId()); } { @@ -1111,12 +1111,13 @@ namespace MobileGL::MG_Backend::DirectGLES { if (bufferObj) { auto* backendResource = BufferImpl::EnsureBufferResource(bufferObj); if (backendResource && backendResource->id != 0) { - BufferImpl::BindBufferId(GL_UNIFORM_BUFFER, backendResource->id); + // glBindBufferBase/Range set the generic GL_UNIFORM_BUFFER binding + // as a side effect, so no separate BindBufferId is needed here. if (range.end == 0) { - g_GLESFuncs.glBindBufferBase(GL_UNIFORM_BUFFER, lastUBOBinding, - backendResource->id); + BufferImpl::BindBufferBaseCached(GL_UNIFORM_BUFFER, lastUBOBinding, + backendResource->id); } else { - g_GLESFuncs.glBindBufferRange( + BufferImpl::BindBufferRangeCached( GL_UNIFORM_BUFFER, lastUBOBinding, backendResource->id, (GLintptr)range.start, (GLintptr)(range.end - range.start)); } @@ -1235,8 +1236,8 @@ namespace MobileGL::MG_Backend::DirectGLES { if (paramsBinding >= 0) { auto* resource = BufferImpl::EnsureBufferResource(drawIndirectBuffer); if (resource && resource->id != 0) { - g_GLESFuncs.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, static_cast(paramsBinding), - resource->id); + BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast(paramsBinding), + resource->id); } } for (GLsizei i = 0; i < drawcount; ++i) { @@ -1282,8 +1283,8 @@ namespace MobileGL::MG_Backend::DirectGLES { if (paramsBinding >= 0) { auto* resource = BufferImpl::EnsureBufferResource(drawIndirectBuffer); if (resource && resource->id != 0) { - g_GLESFuncs.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, static_cast(paramsBinding), - resource->id); + BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast(paramsBinding), + resource->id); } } for (GLsizei i = 0; i < drawcount; ++i) { @@ -3871,6 +3872,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // Conservatively drop the redundant-glUseProgram guard: re-issuing one bind // after a MakeCurrent is cheaper than trusting a possibly-reset context. PrgramImpl::g_lastUsedBackendProgramId = 0; + BufferImpl::InvalidateIndexedBufferBindingCache(); // eglSwapInterval requires a current context; a request made while none was // current (and dropped by the driver) is retried here. ApplyRequestedSwapInterval(); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 4ae282b2..c517b186 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -749,6 +749,47 @@ namespace MobileGL::MG_Backend::DirectGLES { g_boundArrayBufferKnown = false; } + namespace { + // Shadow of the GL indexed buffer bindings so redundant glBindBufferBase/Range + // (same index + id + range) are skipped. isBase distinguishes a whole-buffer + // base bind from a sub-range bind. Fresh/reset context: every point is base(0) + // == unbound, which matches the GL default. + struct IndexedBufferBinding { + Uint id = 0; + GLintptr offset = 0; + GLsizeiptr size = 0; + Bool isBase = true; + }; + constexpr SizeT kMaxIndexedBufferBindings = 64; + IndexedBufferBinding g_indexedUBOBindings[kMaxIndexedBufferBindings]; + IndexedBufferBinding g_indexedSSBOBindings[kMaxIndexedBufferBindings]; + IndexedBufferBinding* IndexedBindingShadow(GLenum glTarget, Uint index) { + if (index >= kMaxIndexedBufferBindings) return nullptr; // out of range: never cache + if (glTarget == GL_UNIFORM_BUFFER) return &g_indexedUBOBindings[index]; + if (glTarget == GL_SHADER_STORAGE_BUFFER) return &g_indexedSSBOBindings[index]; + return nullptr; + } + } // namespace + + void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id) { + auto* s = IndexedBindingShadow(glTarget, index); + if (s && s->isBase && s->id == id) return; + g_GLESFuncs.glBindBufferBase(glTarget, index, id); + if (s) *s = {id, 0, 0, true}; + } + + void BindBufferRangeCached(GLenum glTarget, Uint index, Uint id, GLintptr offset, GLsizeiptr size) { + auto* s = IndexedBindingShadow(glTarget, index); + if (s && !s->isBase && s->id == id && s->offset == offset && s->size == size) return; + g_GLESFuncs.glBindBufferRange(glTarget, index, id, offset, size); + if (s) *s = {id, offset, size, false}; + } + + void InvalidateIndexedBufferBindingCache() { + for (auto& b : g_indexedUBOBindings) b = {}; + for (auto& b : g_indexedSSBOBindings) b = {}; + } + void TrimBufferPool() { const std::lock_guard lock(g_poolMutex); if (g_pooledBytes <= kMaxPoolBytes) return; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index e0607563..c6c7e2e6 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -177,6 +177,13 @@ namespace MobileGL::MG_Backend::DirectGLES { // glBindBuffer with a redundant-bind cache for GL_ARRAY_BUFFER. void BindBufferId(GLenum target, Uint id); void InvalidateArrayBufferBindingCache(); + // Redundant-bind cache for INDEXED buffer bindings (glBindBufferBase/Range on + // GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER): skips the GL call when the + // (id, range) already at that index matches, like the array-buffer/texture/ + // sampler caches already do. Invalidated on MakeCurrent (context may reset). + void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id); + void BindBufferRangeCached(GLenum glTarget, Uint index, Uint id, GLintptr offset, GLsizeiptr size); + void InvalidateIndexedBufferBindingCache(); // Buffer-storage pool maintenance. TrimBufferPool evicts over-budget entries // (called once per frame from Present); ClearBufferPool drops all pooled ids // without glDeleteBuffers (called when the ES context is going away).