First wave of the advertised-extension CTS campaign (targeted caselist: the
glcts groups of every extension both backends advertise, 4867 cases across the
KHR-GL41..46 namespaces). All frontend, shared by both backends:
- Non-square float matrix uniforms actually upload: glUniformMatrix{2x3,3x2,
2x4,4x2,3x4,4x3}fv and the six glProgramUniformMatrix* twins were
validate-only no-ops; they now write column-at-a-time at the global UBO's
16-byte std140 column stride, honouring transpose. glUniformMatrix2fv had
the sibling bug - mat2 written as 4 contiguous floats put column 1 at byte
8 instead of 16. The readback path only ever un-padded mat3, so
glGetUniformfv is fixed for mat2, mat3x2 (previously mis-gathered) and
every non-square shape, with the bounds check widened to the padded span.
- glBindBufferRange validates offset/size at last: size <= 0, offset < 0,
SSBO and UBO offset alignment, transform-feedback offset AND size
multiples of 4 - all before any state write (a negative offset used to
reach Range1D unchecked). glBindBuffersRange inherits per element, with
the ARB_multi_bind up-front [first, first+count) checks added to the
BindBuffersBase/Range and BindSamplers prologues.
- BufferSubData's second, wrong mapped-overlap test deleted (it rejected
every write at or after a mapped range's start, mapped or not); the state
layer's assert relaxed to the same half-open intersection the frontend
checks. BufferStorage error precedence fixed: no-bound-buffer now beats
bad-size/flags.
- glSamplerParameteri accepts the full GL_NEVER..GL_ALWAYS compare-func
range (NEVER/LESS/EQUAL were rejected by a wrong lower bound).
glBindSampler's unit gate uses GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS instead
of the frontend array capacity, shared with glBindSamplers by construction.
- Getters: GL_MAX_SHADER_STORAGE_BLOCK_SIZE in glGetIntegerv; atomic-counter
buffer limits; all 11 per-unit GL_TEXTURE_BINDING_* plus GL_SAMPLER_BINDING
in glGetIntegeri_v; GL_VERTEX_ATTRIB_BINDING/_RELATIVE_OFFSET across the
vertex-attrib query family; glGetFloati_v/glGetDoublei_v implemented (were
stubs); KHR_debug limits raised to spec floors.
- glCreateShader records INVALID_ENUM for an unknown type (it previously
handed out a usable name with no error at all); glCreateShaderProgramv
validates count up front. glDispatchCompute/Indirect validate work-group
counts, offset alignment and indirect-buffer presence.
- glVertexAttribIFormat & friends take a positive integer-type whitelist -
GL_FLOAT/GL_HALF_FLOAT/GL_DOUBLE/GL_FIXED no longer slip through as
integer attributes.
Gate (headless Mesa, default config = async on): 570/570 unit at default and
with the kill switch; ext caselist Espryt 76.29% -> 77.87% (+81 fixed, 6
crashes -> 0, the whole list now runs in one glcts process), Magma 75.94% ->
77.58% (+80 fixed, 0 newly broken); KHR-GL33 full mustpass lost nothing
(9884/9886, the 2 known Mesa-drift failures); retrace smoke clean (the
bsl-GLES miss is the documented golden drift, bit-identical on the pristine
baseline). The 4 DirectGLES direct_state_access.renderbuffers_storage* cases
that turned red are a PRE-EXISTING GL_FRAMEBUFFER_SRGB cross-test leak,
A/B-proven on an unpatched 2e6fc1ff build - wave 1 removed the two accidental
maskers (a crash partition and a failing case whose error path reset the
state). Fixing the leak itself is queued.
The binding-point half of ARB_vertex_attrib_binding was implemented, but nothing
outside it could see the result. glGetIntegerv answered GL_MAX_VERTEX_ATTRIB_BINDINGS,
GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET and GL_MAX_VERTEX_ATTRIB_STRIDE with a hardcoded
0 and a comment saying the entry points were stubs, which they no longer are. An
application that sizes its loops off those limits therefore saw none, and every
"bindingindex must be less than MAX_VERTEX_ATTRIB_BINDINGS" check silently accepted
everything because the limit it validated against was not the one it reported.
The indexed getters answer GL_VERTEX_BINDING_{BUFFER,DIVISOR,OFFSET,STRIDE} from the
bound vertex array now, and the non-indexed getter reports them as indexed-only rather
than returning a fabricated 0.
glVertexAttribPointer is defined in terms of the binding model: it also points the
attribute at its own binding point and gives that point the buffer, the pointer as the
offset and the effective (never zero) stride. MobileGL resolved the pointer form
straight into the flat attribute view and left the binding point untouched, so
GL_VERTEX_BINDING_OFFSET read back 0 for every attribute set up the classic way. The
flat view keeps the raw stride, because GL_VERTEX_ATTRIB_ARRAY_STRIDE reports that
argument verbatim, so the binding point is recorded alongside it rather than resolved
from it. glVertexAttribDivisor likewise now moves the binding point's divisor.
The by-name entry points reject vertex array 0. MobileGL keeps a real object at index 0
for the compatibility paths, so the name validation used to let the default vertex array
through a direct-state-access call that has no such thing.
glVertexAttribFormat and friends validated with the pointer-only subset, which reports
GL_BGRA as an out-of-range size instead of applying the BGRA rules, and never saw
relativeoffset at all. They share the full format validation now, which also grew the
GL_UNSIGNED_INT_10F_11F_11F_REV rules - that type has no DataType of its own, so it has
to be recognised before the conversion turns it into Unknown and reports the wrong error.
glVertexAttribLFormat and glVertexArrayAttribLFormat were stubs. They validate their
arguments now and then report that 64-bit vertex attributes are unsupported, which is
honest; silently accepting a format that can never be used is not.
Takes direct_state_access.vertex_arrays_* from 12 to 17 of 19 on both backends.
glVertexAttribPointer now accepts the GL 3.3 Core packed types
GL_INT_/GL_UNSIGNED_INT_2_10_10_10_REV and the GL_BGRA size, clearing the
two long-standing "// TODO: implement GL_BGRA support" markers. Adds the
format end to end across the frontend, VAO state, and both backends.
- DataType: add Int2101010Rev / Uint2101010Rev with GLToMG / MGToGL /
MGToStr converter cases.
- Validation (ValidateVertexAttribFormat): the full glVertexAttribPointer /
glVertexAttribIPointer error table -- size is 1..4 or GL_BGRA (else
INVALID_VALUE, which takes precedence); a packed type requires size 4 or
GL_BGRA (else INVALID_OPERATION); GL_BGRA requires GL_UNSIGNED_BYTE or a
packed type AND normalized == GL_TRUE (else INVALID_OPERATION); the
integer path rejects packed types (INVALID_ENUM) and GL_BGRA size
(INVALID_VALUE).
- VAO: store GL_BGRA as size 4 plus a new IsBgra flag (reset on the
binding-format path).
- DirectVulkan: map the packed/BGRA formats to
VK_FORMAT_A2B10G10R10_* (normal) and VK_FORMAT_A2R10G10B10_* /
VK_FORMAT_B8G8R8A8_UNORM (BGRA reversed), fold IsBgra into the pipeline
hash, and size packed/BGRA elements as one 4-byte word via
GetAttributeByteSize. (Vulkan *_SNORM decodes with the GL 4.2 symmetric
rule, a documented deviation from the 3.3 signed formula.)
- DirectGLES: round-trip the packed enum through the loader, pass GL_BGRA
as the driver size argument, and size client uploads with the packed
4-byte word.
Tests: 4 VertexArrayTest cases covering packed/BGRA storage and the full
float/integer error table; the packed-size hard-fail is mutation-verified.
VertexArrayTest 42/42, SanityTest 30/30, library builds clean.
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).