Adreno/Qualcomm report a huge maxPerStageDescriptorSampledImages, and the
per-stage texture-unit limits were clamped only to the combined array capacity
(TextureState::MAX_TEXTURE_IMAGE_UNITS = 192). glGetIntegerv thus advertised 192
for GL_MAX_TEXTURE_IMAGE_UNITS, but host code treats it as an array bound:
Minecraft's Blaze3D GlStateManager.TEXTURES[] holds 128 entries and Iris iterates
[0, GL_MAX_TEXTURE_IMAGE_UNITS) over it in CompositeRenderer.renderAll, throwing
ArrayIndexOutOfBoundsException: Index 128 out of bounds for length 128.
Introduce MAX_PER_STAGE_TEXTURE_IMAGE_UNITS = 32 (desktop-driver value) and clamp
the per-stage sampler limits to it in both backends (DirectGLES previously did not
clamp at all), keeping the combined limit at the array capacity. Update SanityTest.
- 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>
- 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>