Bind a fragment output to both a color number and a color index (0 or 1
for dual-source blending), and report the bound index back through
glGetFragDataIndex.
- ProgramObject now tracks a per-output color index alongside the
location: SetExplicitFragmentOutIndex stores it, it is snapshotted into
the linked map at link time (like the location map), and
GetFragmentDataIndex returns it (0 by default) for an active output.
- glBindFragDataLocation becomes glBindFragDataLocationIndexed with index
0, matching the GL definition, so it also resets a previously-bound
index to 0.
- Validation: index must be 0 or 1 (GL_INVALID_VALUE); colorNumber is
bounded by GL_MAX_DRAW_BUFFERS for index 0 and GL_MAX_DUAL_SOURCE_DRAW_BUFFERS
(reported as 1) for index 1 (GL_INVALID_VALUE); a gl_ name is
GL_INVALID_OPERATION.
- glGetFragDataIndex now returns the real bound index instead of a
hardcoded 0.
The index is tracked for reflection but is not yet plumbed into dual-source
blend rendering, and shader-side layout(index=) qualifiers are not
reflected -- both documented at the call sites.
Tests: index round-trip through a re-link (bind 1 -> GetFragDataIndex == 1;
glBindFragDataLocation resets to 0), plus the validation error table;
mutation-verified end to end. ProgramTest 24/24.
glBindFragDataLocation, glGetFragDataLocation and glGetFragDataIndex each
recorded a redundant GL_INVALID_OPERATION on top of the error that
TryToGetProgramObject already recorded (GL_INVALID_VALUE for an unknown
name, GL_INVALID_OPERATION for a non-program object). One bad call thus
queued two errors, so an app calling glGetError twice saw a spurious
second error, and any following code that expects a clean error queue
(e.g. a later test) picked up the stale one.
Drop the second RecordError from all three call sites and rely on the
single error TryToGetProgramObject already reports -- matching the clean
`if (!programObject) return;` pattern the rest of GL_Program.cpp uses. The
first, app-visible error is unchanged; only the redundant second is gone.
ProgramTest's invalid-handle case now asserts exactly one error (mutation-
verified: reintroducing the second record fails it) and keeps a defensive
error-queue drain. ProgramTest 24/24.
Fill the stubbed GL 3.3 Core glGetFragDataIndex, mirroring its already-
implemented sibling glGetFragDataLocation: validate the program object and
link status, then return the fragment color index the name binds to.
Every active user-defined output uses color index 0. MobileGL does not yet
track dual-source (index 1) bindings -- glBindFragDataLocationIndexed and
the layout(index = 1) qualifier are unsupported -- so the result is exact
for any program that does not use dual-source blending; a name that is not
an active output (including gl_ built-ins) returns -1.
Tests: assertions on the existing linked-program test (valid output -> 0,
unknown name -> -1) plus a standalone invalid-handle case. The invalid-
handle test drains the error queue it produces so no stale error leaks
into a later test (the ProgramTest fixture does not reset it). ProgramTest
24/24.
Completes the uniform-block reflection chain: glGetUniformIndices, glGetActiveUniformName
and glGetActiveUniformBlockiv were already implemented; glGetActiveUniformsiv was the last
stub. Supports all 8 GL 3.3 Core pnames:
* GL_UNIFORM_TYPE / SIZE / NAME_LENGTH / BLOCK_INDEX / OFFSET / ARRAY_STRIDE come straight from
glslang's TObjectReflection (the same reflection the existing uniform queries use).
* GL_UNIFORM_IS_ROW_MAJOR from the member's TType layout qualifier, guarded by isMatrix() so a
scalar in a layout(row_major) block does not wrongly report 1.
* GL_UNIFORM_MATRIX_STRIDE is derived: glslang exposes no matrix stride, so it is computed from the
std140 rule (each column/row vector rounded up to a vec4), which matches the std140 layout
MobileGL's SPIR-V path emits. Evaluates to 16 for every GL 3.3 float matrix.
The -1-vs-0 distinction is handled explicitly: OFFSET / ARRAY_STRIDE / MATRIX_STRIDE / BLOCK_INDEX
return -1 for a default-block uniform (glslang gives arrayStride 0 there, so it is gated on block
membership), while ARRAY_STRIDE / MATRIX_STRIDE return 0 for a non-array / non-matrix member that IS
in a block. Errors: GL_INVALID_VALUE for uniformCount<0, any index >= active uniform count, or a
never-generated program name; GL_INVALID_OPERATION for a live shader name; GL_INVALID_ENUM for an
unaccepted pname (e.g. the GL 4.2 GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX). All validation runs before
any write, so params is untouched on error. There is no "not linked" error -- an unlinked program has
zero active uniforms, so any index raises GL_INVALID_VALUE.
Also fix GetActiveUniformArraySize, which returned glslang's TObjectReflection.size verbatim: that
field only carries the element count for a non-block array and reports 1 for a block array member,
so GL_UNIFORM_SIZE (and glGetActiveUniform's size out-param, and glGetProgramResourceiv's
GL_ARRAY_SIZE) wrongly reported 1 for an array inside a UBO. Take the count from the TType instead,
which is authoritative for both cases.
Covered by 3 ProgramTest cases (std140 block with scalar/array/mat4 + a default-block sampler, a
row_major variant, and the six error cases) that link real shaders and assert every pname value.
GL 3.3 Core: a shader input whose generic attribute array is disabled reads that
attribute's current value (per-context state, default (0,0,0,1)). Four defects made
that path non-conformant, three of them silently.
* Out-of-bounds current-value reads. m_currentVertexAttributes held 16 entries while
the DirectVulkan draw path walked shader input locations 0..31 and GL_MAX_VERTEX_ATTRIBS
was advertised straight from the device (commonly 32). The only guard was MOBILEGL_ASSERT,
which expands to nothing outside debug builds. Grow the storage capacity to 32, advertise
min(device limit, capacity), validate against that dynamic limit, and give the accessors
real runtime bounds checks. Replace the literal 32 loops with the constant, and pin
MAX_VERTEX_ATTRIBS to the Uint32 mask width and to vertexInputTypes' bound with
static_asserts so the two can no longer drift apart -- that drift was the bug.
* DirectGLES never fed current values to the driver. Values were stored in MG_State only,
so a disabled attribute always rendered as the ES driver's own untouched (0,0,0,1) while
DirectVulkan rendered it correctly: identical GL code, different pixels per backend.
Add SyncCurrentVertexAttributeValues() to the draw prologue, and hoist the
glType -> (base type, component count) dispatch into MG_State::GLState so both backends
resolve the semantics from one place instead of it living inside VulkanRenderer.
* Enabled arrays the backend could not map were silently demoted to the current value.
ToVkVertexFormat had no DataType::Float16 case, so a GL_HALF_FLOAT array fell to
VK_FORMAT_UNDEFINED, dropped out of the vertex input state, and became indistinguishable
from a disabled array: the geometry rendered a constant colour with GL_NO_ERROR. Add the
Float16 mapping, track an unsupportedAttribMask, and hard-fail the draw before pipeline
creation so no synthetic attribute is baked into a cached VkPipeline.
* glGetVertexAttrib{fv,iv,Iiv,Iuiv}(GL_CURRENT_VERTEX_ATTRIB) returned before any index
validation, reading past the array instead of raising GL_INVALID_VALUE.
Also resolve ProgramObject::DoReflection's "TODO: get from backend" 16-location clamp,
which capped the new DirectGLES sync at locations 0..15; report GL_MAX_VERTEX_ATTRIBS
through the same helper the validators use, so the clamp cannot be bypassed; and bound
vertex binding indices by the same dynamic limit, since the default attribute -> binding
mapping is the identity.
Add a "Vertex attributes" driver POST row to both backends: FAIL below the GL 3.3 Core
minimum of 16, WARN above MobileGL's storage capacity (clamped, extra attributes unusable),
PASS in between -- making the driver/host mismatch that caused the out-of-bounds read
visible instead of silently swallowed.
Covered by 7 new regression tests (each verified to fail against the previous behaviour).
- 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>
- deal with legacy GLSL syntax (attribute/varying/gl_FragColor/texture2D/etc.)
- implement glGet GL_SHADER_SOURCE_LENGTH, and make sure returns
original shader source
- expose proper extensions (GL_ARB_depth_texture)
- support env var MOBILEGL_LOG_FILE_PATH
- unit tests to test against those changes