[Perf] (MG_Backend/DirectVulkan, MG_State): stop re-deriving per-draw state

- Memoize the program content hash on ProgramObject (keyed by the backend
  state version + compile flags; relinking and binding changes invalidate
  it) and the vertex-input hash on VertexArrayObject (keyed by a new
  aggregate config version bumped by every attribute mutation). Full-SPIRV
  XXH64 hashing fell from 13.7% to 1.4% of the render thread.
- ProgramObject also gains a link version and a global-UBO content version
  (bumped by uniform writes and on relink, wrap-safe around the backends'
  "never uploaded" sentinel) for backends to gate uploads and link caches.
- Reuse member scratch vectors in SetupDraw, UploadAndBindVertexBuffers,
  GetOrCreatePipeline and BindProgramUniformBuffers instead of allocating
  per draw (~12% of render-thread time was in the allocator).
- Replace hot-path dynamic_cast with AsMipmapTexture (storage-type tag +
  static_cast); TextureObjectMipmap is the only Mipmap-tagged branch.
- Register/prune texture aliases only when a new (texture, lifetimeId)
  identity appears instead of scanning the entire alive map on every
  sampled-texture sync.
- Make the fallback VkPresentModeKHR log strings report the actual mode.

Vanilla render-thread share of libMobileGL dropped from 48% to 35% on
DirectVulkan (simpleperf, Adreno 830).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 12:12:50 +00:00
co-authored by Claude Fable 5
parent 08e808ef20
commit bd208783d7
15 changed files with 166 additions and 24 deletions
@@ -675,7 +675,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Vector<TextureIdentity> staleAliases;
staleAliases.reserve(m_aliveObjects.size());
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
if (it->first.texture != texture) {
continue;
@@ -698,12 +697,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto aliveIt = m_aliveObjects.find(identity);
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
EraseTrackedTexture(aliveIt->first);
aliveIt = m_aliveObjects.end();
}
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
if (liveTexture && liveTexture.get() == &texture) {
m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
PruneStaleTextureAliases(&texture);
// Only (re)register and prune when this (texture, lifetime) pair is new: stale
// aliases can only come into existence through an address reuse, which by
// construction introduces a new identity. Doing this unconditionally made every
// sampled-texture sync scan the entire alive-texture map per draw.
if (aliveIt == m_aliveObjects.end()) {
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
if (liveTexture && liveTexture.get() == &texture) {
m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
PruneStaleTextureAliases(&texture);
}
}
auto it = m_textureResources.find(identity);
@@ -1033,7 +1039,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
auto* mipTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(&texture);
auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
if (!mipTexture) {
MGLOG_D("%s: not TextureObjectMipmap", __func__);
return false;
@@ -1579,7 +1585,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
IntVec3& outTexelSize,
SizeT& outByteSize,
Uint32& outMipLevelCount) {
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
if (!mipTexture) {
MGLOG_D("%s: not TextureObjectMipmap", __func__);
return false;
@@ -1639,7 +1645,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0");
Uint32 definedMipLevels = mipLevels;
if (const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture)) {
if (const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture)) {
const auto& targets = texture.GetUploadTargets();
for (const auto target : targets) {
const Uint32 uploadMipLevels = GetUploadMipLevelCount(*mipTexture, target);