Commit Graph
63 Commits
Author SHA1 Message Date
BZLZHH ba81ee114e [Feat] (MG_Backend, MG_Impl, MG_Util): attach one layer of any layered texture on DirectVulkan
Whether a backend can attach a single layer of a texture to a framebuffer was one
Bool, so it could only give the most conservative answer any target needed.
DirectVulkan therefore declined every layer of every target and
direct_state_access.framebuffers_texture_layer_attachment failed with 542
messages across four targets.

The three ways a GL layer maps onto Vulkan are independent capabilities, so the
flag becomes a per-TextureTarget mask. A 2D or 2D multisample array layer IS a
VkImage array layer and needed nothing but the gate opened. A cube map array is
one 2D image with arrayLayers = 6 * cubeCount and CUBE_COMPATIBLE, which is a
shape VkTextureManager simply did not have - it is declined softly when the depth
is not a whole number of cubes or the level is not square, because that function's
Bool return exists for unrepresentable shapes and asserting there would abort on
ordinary input, GL_PROXY_TEXTURE_CUBE_MAP_ARRAY above all. A 3D texture's layer is
a z slice, which needs a 2D-array-compatible image and a per-slice clear, because
vkCmdClearColorImage cannot address a subset of a 3D image's slices - a render
pass whose only content is its LOAD_OP_CLEAR can, since its attachment is a 2D
view over that one slice.

VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is asked for per format and withdrawn per
format, mirroring the MUTABLE_FORMAT pattern already in this file: the capability
is per format+usage, so a single global probe answers a different question than
the one the frontend goes on to ask. Losing it costs per-slice attachment for that
format; failing creation would lose the texture.

Three things found on the way that are not the headline:

glFramebufferTextureLayer, the non-DSA twin, had no gate at all and additionally
refused cube map arrays that GL 4.5 requires it to accept. GL 4.6 core 9.2.8 makes
the two entry points equivalent, so they now decline in the same places - leaving
one ungated is what let an unrepresentable attachment reach the renderer.

ComputeFullMipLevelCount takes max(x, y, z), and for every array shape z is the
layer count rather than a mip-able axis, so a 4x4 array with 192 layers asked for
six mip levels on an image whose legal maximum is three
(VUID-VkImageCreateInfo-mipLevels-00958). Only the image's own extent can bound
it. lavapipe had been letting that through.

A layered GL clear queues layerCount = depth, which is illegal for a
VK_IMAGE_TYPE_3D image (VUID-vkCmdClearColorImage-baseArrayLayer-01472 pins it to
0/1, read as the whole mip level) and the old code passed it straight through.

Takes framebuffers_texture_layer_attachment green on DirectVulkan, so the whole
direct_state_access suite is 371/371 there; Espryt stays 370/371, the remaining
case being the fp64 one it declines by design.

Known and deliberately not fixed here, with a FIXME at the site:
KHR-GL44/45/46.geometry_shader.layered_framebuffer.clear_call_support now fails on
DirectVulkan - a layered clear of a 3D texture reads back zeros. Those cases exist
only in the GL44+ lists, above the 4.0 this backend reports. An A/B of a 6935-case
subset (cube map array, texture storage, framebuffer, 3D, the full DSA suite and
the GL33 texture group) is otherwise clean on both backends: 16 cases fixed and
none broken on Espryt, 15 fixed and those 2 broken on Magma, and zero difference
anywhere at GL 4.0 or below. The FIXME records which causes were already ruled out
by bisection so the next reader does not repeat them.
2026-08-05 12:07:04 -04:00
BZLZHH 34f09291da [Feat] (MG_State, MG_Backend, MG_Util): feed a 64-bit vertex attribute on DirectVulkan
glVertexAttribLFormat validated its arguments and then refused unconditionally
with "64-bit vertex attributes are not supported", so
direct_state_access.vertex_arrays_attribute_format failed every GL_DOUBLE
subcase on both backends - the format never landed, the draw fetched whatever
the attribute held before, and the captured values came back as reinterpreted
garbage.

The attribute is now real state. IsLong is its own bit rather than being
inferred from Float64, because glVertexAttribFormat(GL_DOUBLE) also reads
doubles - it just asks for them converted to float - so the type alone cannot
tell the two apart. It participates in the format comparison, so an L-format
call over a plain one still bumps the version, and glVertexAttribPointer clears
it inside the mutation block so the clear and the bump stay atomic.
GL_VERTEX_ATTRIB_ARRAY_LONG stops being hardcoded false, and the pname is now
accepted by the attribute queries at all.

Support is detected, never assumed. SupportsFloat64VertexAttributes comes from
VkPhysicalDeviceFeatures::shaderFloat64 on DirectVulkan and is false on
DirectGLES - not a driver question there and never will be, since ES has no
GL_DOUBLE vertex format and ESSL has no fp64 type to consume one with. A backend
without it declines in the entry point, with the GL error and a log line naming
the reason, rather than accepting state no draw could honour. Both cases get a
DriverPost row so the loss is named at startup instead of at draw setup.

On DirectVulkan the attribute deliberately does not use VK_FORMAT_R64*_SFLOAT:
those are optional and lavapipe advertises zero features for all four of them.
It is fetched as its 32-bit word pair (R32G32_UINT / R32G32B32A32_UINT) and
bitcast back to double in the shader by a new SPIR-V pass, which is bit-exact
and needs no format capability at all. The pass re-declares the input as uvec2 /
uvec4, demotes the original variable to a Private global and seeds it once at
the top of the entry point, so every existing load keeps its id and its double
type and no other instruction is rewritten. Both halves branch on nothing but
"is this attribute long", so they cannot disagree - and if the pass ever fails,
the assertion fires rather than letting a UINT format sit under a double input.
The pointer types are all created before any variable that names them and the
demoted variable is moved after them, since the types-and-variables section may
not forward-reference a type.

dvec3/dvec4 are declined rather than fetched wrong: six or eight uint32
components have no single VkFormat, and GL spreads such an input over two
attribute locations, which the location-per-index model here does not express.

Fixes vertex_arrays_attribute_format on Magma (369/371). On Espryt it stays
failing, now as a detected and explained decline rather than a blanket refusal.
2026-08-05 08:49:23 -04:00
BZLZHH 9eda2147b1 [Fix] (MG_Impl, MG_Backend): let the backend that can honour a layered attachment have it
NamedFramebufferTextureLayer declined every attachment but layer zero, on both
backends. That was right for DirectVulkan, which maps a GL layer onto a Vulkan
array layer with no notion of a 3D depth slice, but wrong for DirectGLES:
SyncAttachmentObject already routes a layered upload target to
glFramebufferTextureLayer with the attachment's layer passed straight through,
and array storage already carries the real layer count into glTexStorage3D. The
one backend that could render to the layer was being told it could not.

The decision now lives in a DynamicBackendParameters flag, so it is the backend
that answers rather than the entry point guessing. DirectGLES sets it when the
driver resolved glFramebufferTextureLayer; DirectVulkan leaves it false until
VkRenderPassManager tells a depth slice from an array layer.

framebuffers_texture_layer_attachment's colour checks now pass on Espryt for 3D,
2D array and 2D multisample array textures - the case still fails there on cube
map arrays, which DirectGLES gives no storage at all, and on the depth and
stencil halves. No case changes on DirectVulkan, which keeps the old behaviour.
2026-08-05 03:40:59 -04:00
BZLZHH c114ce750b [Feat] (MG_Backend): advertise OpenGL 4.0 on both backends
Both backends stopped their advertised version list at V_OpenGL33, so an
application - or the CTS - asking what MobileGL supports was told 3.3 even
though the 4.0 entry points and the KHR-GL40 suite already pass on both.
Adding V_OpenGL40 lets that work be reached through the ordinary version query
instead of only through the individual ARB extension strings.
2026-08-05 13:19:41 +08:00
BZLZHH 58c17f85a5 [Fix] (MG_State, MG_Backend): start TEXTURE_COMPARE_FUNC at LEQUAL
SamplerParameters defaulted compareFunc to ALWAYS, but GL 4.6 core table 23.18
and GLES 3.2 table 21.16 both say the initial value is LEQUAL - for sampler
objects and for the sampler state a texture object carries alike. Every freshly
created texture and sampler therefore answered GL_ALWAYS to
glGetTextureParameteriv(GL_TEXTURE_COMPARE_FUNC).

The Vulkan backend had been papering over it: ResolveCompareFunc substituted
LESS_EQUAL whenever a depth texture was sampled in compare mode and the func
still read ALWAYS, which fixed the rendering but also made an explicitly
requested GL_ALWAYS unreachable. With the default corrected that special case is
both unnecessary and wrong, so it is gone and the compare op is taken straight
from the sampler.

Takes direct_state_access.textures_defaults from failing to passing on both
backends.
2026-08-05 01:13:04 -04:00
BZLZHH 19932f9e49 [Feat] (MG_Impl, MG_Backend): implement the integer direct state access framebuffer clears
glClearNamedFramebufferiv and glClearNamedFramebufferuiv were stubs, so a clear through
them was silently dropped and the attachment kept whatever it held. Their float siblings
were already implemented, which is what made the gap look like a rendering bug rather than
a missing entry point.

Which buffers they accept is narrower than glClearNamedFramebufferfv and differs between
the two: signed values clear COLOR or STENCIL, unsigned only COLOR (GL 4.6 core 17.4.3.1).
Only the colour buffer is indexed, so a stencil clear naming any drawbuffer other than 0 is
INVALID_VALUE rather than merely ignored, and anything else is INVALID_ENUM. Resolving the
framebuffer by name goes through the same helper the float forms use, which is what reports
INVALID_OPERATION for a name that is neither zero nor an existing framebuffer.

Both backends express them the way they already express the float forms: DirectGLES binds
the named framebuffer and forwards to glClearBuffer*, Magma queues the payload against the
named framebuffer rather than the bound one.

direct_state_access.framebuffers_clear_errors passes on both backends, and
framebuffers_clear passes on Espryt. Magma still fails that one, for a separate reason on
the materialization side rather than in these entry points.
2026-08-04 23:41:28 -04:00
BZLZHH bb582203d9 [Feat] (MG_Impl, MG_State, MG_Util): attach a buffer texture to a range of its buffer
glTexBufferRange, glTextureBuffer and glTextureBufferRange were all stubs, so a
buffer texture could only ever be attached through glTexBuffer -- by binding, and
always to the whole buffer.

Give the buffer texture the window it is supposed to address. The non-range forms
record it as offset 0 with a whole-buffer sentinel rather than the size the buffer
happens to have, so a later respecify keeps being followed instead of freezing the
texture at yesterday's size. All four entry points now share one attach path,
differing only in how they name the texture: by binding for the target forms, by
name for the DSA ones.

Both backends honour the window: DirectVulkan offsets and clamps the buffer view,
DirectGLES uses glTexBufferRange when the texture names a sub-range and keeps
plain glTexBuffer for the whole-buffer case, which also works on a driver without
the range entry point.

GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT reported 0 with a comment explaining that the
range entry points were stubbed. It now reports what the device actually requires
-- minTexelBufferOffsetAlignment on Vulkan, the driver's own value on GLES -- and
the range entry points enforce it. Zero was never a legal answer; the minimum is
1, and an application that trusted it would have built unaligned offsets.
2026-08-04 20:53:53 -04:00
BZLZHH b95fcb7bca [Feat] (MG_State, MG_Impl, DirectGLES): implement glPatchParameteri
GL_PATCH_VERTICES decides how many vertices one tessellation patch consumes, and
glPatchParameteri was a stub - so the value stayed at the driver's default of 3 no
matter what the application asked for. KHR-GL40.texture_gather.gather-tesselation-shader
sets it to 1 and then draws a single patch: with the request dropped the draw had too
few vertices for one patch, produced nothing at all, and the case read back the clear
colour.

The value is context state on both sides and ES 3.2 spells the entry point exactly the
same way, so it is stored in the render state (where glGetIntegerv(GL_PATCH_VERTICES)
now finds it) and forwarded. Validation needs the real bound, so GL_MAX_PATCH_VERTICES
and GL_MAX_TESS_GEN_LEVEL are probed off the host driver alongside the other limits and
answered from there too; the defaults are the GL 4.0 core minimums.

KHR-GL40.texture_gather is now 75/75.
2026-08-04 10:58:14 -04:00
BZLZHH 2dcc15bb0e [Feat] (MG_Impl, DirectGLES): advertise GL_ARB_get_program_binary with no binary format
glProgramParameteri is not core before GL 4.1, so in the 4.0 context the CTS runs it
only exists through GL_ARB_get_program_binary or GL_ARB_separate_shader_objects.
MobileGL advertised neither, so dEQP's loader left the entry point null - and
KHR-GL40.api.coverage, which registers glProgramParameteri from GL 3.2 upwards, called
straight through the null pointer and took the process down.

GL_NUM_PROGRAM_BINARY_FORMATS was already 0, and the extension explicitly allows an
implementation to support no binary format at all; that is the honest state of things
here, since a MobileGL program is a glslang link plus a per-backend translation with no
serialised form. So the extension is advertised for what it really provides:
glProgramParameteri stores GL_PROGRAM_BINARY_RETRIEVABLE_HINT (reported back by
glGetProgramiv alongside a GL_PROGRAM_BINARY_LENGTH of zero), glGetProgramBinary is the
INVALID_OPERATION the spec requires when that length is zero, and glProgramBinary
rejects every format with INVALID_ENUM and leaves the program's LINK_STATUS false.

Applications that ask for a binary get the documented "no formats" answer and fall
back, which is what they already had to do - only now they can ask.
2026-08-04 10:27:23 -04:00
BZLZHH 76f37a18e6 [Feat] (MG_State, MG_Impl, DirectGLES): transform feedback objects, pause/resume and the special capture names
GL 4.0 folds ARB_transform_feedback2 and _3 into core, and neither existed:
glGenTransformFeedbacks, glBindTransformFeedback, glDeleteTransformFeedbacks,
glIsTransformFeedback, glPause/ResumeTransformFeedback, the whole
glDrawTransformFeedback family and glBegin/EndQueryIndexed were all stubs, and
gl_NextBuffer / gl_SkipComponents1..4 failed the link as "not an output of the vertex
stage". Seven KHR-GL40.transform_feedback* cases failed on it, three of them by
leaving a capture open at deinit and taking the process down.

Objects. The capture state and the indexed GL_TRANSFORM_FEEDBACK_BUFFER bindings are
object state, but the context keeps one live copy of both, which is what every
existing reader - each backend's per-draw sync, the drawing and getter paths - is
written against. Rather than teach all of them about objects, a bind saves the live
copy into the outgoing object and restores the incoming one's. Object 0 is the
default object and needs no seeding; operator[] materialises the rest on first touch.

Pause. A paused span captures nothing, and three rules key off that: a draw is exempt
from the capture primitive-mode match, it feeds PRIMITIVES_GENERATED but not
TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, and glUseProgram is allowed again (that last
one was already refused for an active capture, correctly for GL 3.3, which has no
pause).

glDrawTransformFeedback replays the vertices the object captured in its last completed
span, recorded at End. "Has a completed span" is tracked separately from that count,
because a completed empty span draws nothing while an object that never ended one is
INVALID_OPERATION. Drawing from the object whose capture is currently open is
deliberately allowed - feeding a result straight into the next span is the point of
KHR-GL40.transform_feedback.draw_xfb_feedbackk_test.

DirectGLES gets a real driver object per frontend object. That is the only reason the
default one would not do: several objects can be paused at once, and a paused span
lives inside the driver's object. The deferred driver-side Begin (still needed - ES
wants the program current and the buffers bound) now also has to be held back while
the span is paused, or a pause taken before the first draw would open the span on that
draw and subject it to the primitive-mode rule it is exempt from.

Special names. gl_NextBuffer and gl_SkipComponents<n> are consumed during varying
resolution and never become varyings of their own, so they only move where the
following ones land - and stay out of the name list the backend declares on its own
driver. ES cannot express the resulting layout at all: it packs every captured varying
into one gap-free record. So when the layout has holes or spans several buffers,
DirectGLES captures into a scratch buffer bound in place of the application's, and
End distributes the records to the offsets GL asked for. Only the bytes a varying
occupies are written, which is exactly what makes the holes keep the contents the
application left there - the property KHR-GL40.transform_feedback3.skip_components
checks.

glBegin/EndQueryIndexed and glGetQueryIndexediv differ from the plain forms only in the
vertex stream they address, so they validate the index and forward. GL_MAX_VERTEX_STREAMS
stays at 1: multi-stream capture needs ARB_gpu_shader5 stream qualifiers that no ES
driver implements, and the CTS cases that need more than one stream check the limit and
skip.

KHR-GL40.transform_feedback, transform_feedback2 and transform_feedback3: 38/38.
2026-08-04 10:18:53 -04:00
BZLZHH 3dc6a1b6db [Fix] (MG_Impl, DirectGLES): answer the texture-gather offset limit queries
glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET) and its GL_MAX_ counterpart fell
through to the default arm of the getter and raised GL_INVALID_ENUM, leaving the
caller's variable untouched - KHR-GL40.texture_gather.api-enums read back the
uninitialised 32764 that happened to be on its stack and failed on the error alone.

Both are core state from GL 4.0 (table 23.53) and from ES 3.1 (table 20.40), so the
value is simply the host driver's, probed alongside the other limits in
FillInGLESCapabilities and carried to the getter through DynamicBackendParameters.
The probe result is widened to the -8/+7 core minimums rather than trusted blindly:
a driver that leaves the out-parameter alone (no ES 3.1, or an enum it ignores) would
otherwise hand us a range narrower than GL 4.0 requires MobileGL to advertise, and
the shaders the CTS builds assume the guaranteed range regardless.
2026-08-04 09:40:15 -04:00
BZLZHH e45f7ae5d4 [Fix] (DirectGLES, MG_Util): keep 16-bit SNORM precision through the widening
GL_RGB16_SNORM widened to GL_RGBA16F to stay renderable as multisample storage, and
a half float's 11-bit mantissa cannot hold a 16-bit signed-normalized channel:
KHR-GL33.texture_swizzle's blue channel came back several units of 32767 away from
the value the reference computes, well outside its one-unit tolerance.

GL_EXT_render_snorm makes the signed-normalized formats colour-renderable on ES, so
widen to GL_RGBA16_SNORM instead wherever it and EXT_texture_norm16 are both
present, and only fall back to the half float otherwise. Threaded through as its own
normalize option so the capability probe and the runtime pick the same format, the
way every other driver-dependent substitution here is decided.
2026-08-02 04:24:23 -04:00
BZLZHH a687873d32 [Fix] (DirectGLES): probe format capabilities on the target ES stores them on
1D, 1D-array and rectangle textures are emulated on ES 2D and 2D-array targets, but
the capability probe kept asking the driver about the desktop-only target itself.
glTexImage2D(GL_TEXTURE_1D, ...) is not something an ES driver has ever accepted, so
those rows of the cache stayed empty - and an empty row reads as "nothing is known",
not as "the format needs help", so no fallback format was ever selected for them.

GL_DEPTH_COMPONENT32 on a 1D texture therefore went to the driver unchanged instead
of as GL_DEPTH_COMPONENT24, and the texture ended up with no storage
(KHR-GL33.texture_swizzle format_idx_65 on both 1D targets read the wrong value for
every pixel).

Probe the ES target the texture will actually live on, while still recording the
capabilities against the target the frontend asked for.
2026-08-02 04:18:20 -04:00
BZLZHH 65dbfa6f26 [Fix] (DirectGLES, MG_Util): widen three-channel formats for multisample textures
GLES has no colour-renderable three-channel format beyond RGB8, so
glTexStorage2DMultisample rejects GL_RGB16 (and the SNORM variants) with
GL_INVALID_ENUM and the texture is left with no storage at all - every draw into it
then hit GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT and every read came back zero.

The existing fallback machinery could not help: it picks one replacement format per
requested format, from the driver's capabilities, and never re-checks that
replacement against the target it is going to be used with. GL_RGB16's fallback is
GL_RGB32F, which is a perfectly legal ES texture format and a perfectly illegal
multisample storage format, and with EXT_texture_norm16 present no fallback was
selected at all.

Add PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget, applied only to
multisample targets, mapping GL_RGB16 to GL_RGBA32F and the three-channel SNORM
formats to GL_RGBA16F. Widening the channel count is safe precisely there and
nowhere else: a multisample texture can never be uploaded to, only rendered into, so
no transfer path has to expand three-channel client data, and the alpha a draw
writes for a three-channel source is already the 1.0 the frontend format implies.

The capability probe recomputes its fallback per target for the same reason, so the
probed format and the format the texture is actually created with stay in agreement.
2026-08-02 04:10:01 -04:00
BZLZHH 9f3cac6691 [Fix] (DirectGLES): report distinct depth/stencil framebuffers as unsupported
GL only requires framebuffers whose depth and stencil attachments refer to the
same image; anything else may be answered GL_FRAMEBUFFER_UNSUPPORTED, and both
backends' real targets do exactly that - DirectVulkan cannot form two separate
attachments at all, and the ES drivers behind DirectGLES return UNSUPPORTED for
a separate depth renderbuffer plus stencil renderbuffer.

The frontend already knew how to detect the configuration, but only consulted it
for DirectVulkan. On DirectGLES it answered GL_FRAMEBUFFER_COMPLETE for a
framebuffer the driver had rejected, so every clear and draw against it was
silently dropped and the results read back as zeros - which is what
KHR-GL3x.packed_depth_stencil.verify_mixed_attachments saw. (That test
explicitly tolerates GL_FRAMEBUFFER_UNSUPPORTED; what it cannot survive is being
told the framebuffer works.)

Turned into a backend capability rather than a backend-type check, probed once
at init from a scratch framebuffer the same way the format-capability cache is,
so a driver that does support the configuration keeps using it. Defaults to
supported, leaving any backend that does not set it on the permissive path.

Fixes KHR-GL3{2,3}.packed_depth_stencil.verify_mixed_attachments for both
formats; DirectVulkan re-verified unchanged at 23/25 pass + 2 not-supported.
2026-08-01 14:05:28 -04:00
BZLZHH 4c7332d5e6 [Fix] (DirectGLES): broadcast legacy gl_FragColor to every draw buffer
Legacy GLSL's gl_FragColor goes to every enabled draw buffer (GL 4.6 15.2.3),
but ShaderSourceProcessor lowers it to a single mg_FragColor output, which only
ever reaches draw buffer 0. Everything past the first attachment kept its
pre-draw contents.

Replicated across the enabled draw buffers with copies at the end of main.
Gated on the count so the ordinary single-target shader is byte-for-byte what it
was: the pass is a no-op below two draw buffers, and the count comes from the
frontend draw framebuffer at program-sync time (not from the backend framebuffer
sync, which only runs later in PrepareForDraw - a program compiled against a
stale count would not be relinked until the draw after the one that needed it).
It joins the snorm/unorm clamp masks as framebuffer state the shader is compiled
against, with the same relink-on-change check.

Also advertises GL_ARB_explicit_attrib_location and GL_ARB_texture_multisample,
which DirectGLES implements for every version it advertises but only listed for
DirectVulkan. Both are core from GL 3.2/3.3 on, so an app targeting 3.0/3.1
reaches them only through the extension string - without the former the CTS
picks an entirely different draw_buffers shader, and without the latter
KHR-GL31.texture_size_promotion.functional crashed outright.

KHR-GL3{0,1,2,3}.draw_buffers.draw_buffers_1 now passes on all four versions,
and texture_size_promotion.functional on GL31 downgrades from a crash to a
(still open) comparison failure.
2026-08-01 13:59:11 -04:00
BZLZHH e9382f5329 [Fix] (DirectGLES): exact transform feedback primitive queries
Two leftovers from the capture passthrough, both only observable with a
geometry shader in the pipeline:

- GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED fell back
  to the frontend's CPU accounting, which counts the primitives the draw call
  assembles and so cannot see a geometry stage's amplification. Both are core ES
  query targets (GL_PRIMITIVES_GENERATED from 3.2 on, gated accordingly so an
  older driver doesn't get a stray GL_INVALID_ENUM), so they now go straight to
  the driver's own counters. Generalized the occlusion-query handle's isOcclusion
  flag into the glBeginQuery target it already had to remember for glEndQuery,
  which is what tells the result read to use the core 32-bit getter.

- FixupGsStripCaptureOrder rewrites captured strip triangles from Vulkan's
  (i, i+1, i+2) order into GL's (i+1, i, i+2). A driver-side capture already
  emits GL order, so the rewrite corrupted it - KHR-GL33.transform_feedback
  .geometry read back the odd triangle rotated one vertex. Skipped when the
  backend owns the capture span.

KHR-GL3{0,1,2,3}.transform_feedback is now 21/21 on Espryt; DirectVulkan
(lavapipe) re-verified at 21/21 for the shared frontend change.
2026-08-01 11:57:18 -04:00
BZLZHH df7d1edeca [Feat] (DirectGLES): implement transform feedback capture
Transform feedback was frontend-only on DirectGLES: glBeginTransformFeedback
just flipped MobileGL's own capture state and the real ES driver was never told
to capture anything, so every capture buffer read back as whatever it held
before the draw (zeros for a fresh glBufferData(NULL)). DirectVulkan drives its
capture from its own draw recording, so the shared GLFunctionsTable had no
entries for the span at all.

Capture now runs on the real driver:

- The backend program declares the capture set with glTransformFeedbackVaryings
  before it links. SPIRV-Cross keeps user output names verbatim in the
  transpiled ESSL, so the frontend's requested names carry over unchanged.
- New GLFunctionsTable Begin/EndTransformFeedback entries hand the span
  boundaries to the backend (null for DirectVulkan, which is unaffected).
- The driver-side begin is deferred to the first draw of the span: ES needs the
  capturing program current and the capture buffers bound, and both only become
  true once PrepareForDraw has run. A span that never draws never touches the
  driver, which is what the GL semantics amount to anyway.
- The end mirrors the captured ranges back into the frontend buffer shadows -
  the GPU wrote them behind the frontend's back, so MapBuffer/GetBufferSubData
  would otherwise still return the pre-draw bytes.

Takes KHR-GL32.transform_feedback from 13/21 to 19/21; the two remaining
failures are the geometry-amplified primitive queries, which still go through
the frontend's CPU accounting.
2026-08-01 11:51:19 -04:00
BZLZHH 0a5d7ceb6c [Feat] (DirectGLES): implement GL_ANY_SAMPLES_PASSED occlusion queries
DirectGLES never registered BeginOcclusionQuery/EndOcclusionQuery, so
the frontend rejected the occlusion query targets entirely; the CTS
tests that use them (e.g. packed_depth_stencil.verify_partial/mixed_
attachments) left a stray GL_INVALID_ENUM that a later, unrelated
glGetError() check would report as its own failure
("Uploading buffer data failed" at gluDrawUtil.cpp:363).

Occlusion queries are core ES3 (glGenQueries/glBeginQuery(GL_ANY_
SAMPLES_PASSED, ...)/glEndQuery/glGetQueryObjectuiv), unlike the timer
queries which need GL_EXT_disjoint_timer_query, so they're wired up
unconditionally (independent of MOBILEGL_DISABLE_TIMERQUERY) using the
same handle-based GetQueryResult64/DeleteBackendQuery plumbing already
shared with timer queries. GetQueryResult64 now reads the 0/1 result
through the core 32-bit glGetQueryObjectuiv getter for occlusion
handles instead of the timer-only 64-bit GL_EXT_disjoint_timer_query
getter, since a driver can fully support core occlusion queries while
lacking that extension entirely.
2026-08-01 11:20:38 -04:00
BZLZHH e86a9bbec5 [Chore] (MG_Backend): restore target GL version to 3.3 2026-07-31 16:45:19 +08:00
swung0x48 7e8c32a063 [Feat] (MG_Backend, MG_Impl): expose experimental GL 4.6 CTS limits 2026-07-30 23:00:13 -04:00
swung0x48 c4254c4bbd [Feat] (WGL): add Windows host layer - drop-in opengl32.dll with WGL over EGLImpl, Win32 window backend plumbing for both backends, ANGLE loader path, and leak-at-exit process teardown 2026-07-26 06:46:29 -04:00
swung0x48 bf7b5755cc [Refactor] (ShaderTranspiler, MG_Backend, MG_Util): gate the subgroup prefix-scan rewrite behind a generic device-quirk registry with GPU vendor detection and MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN override, warn on template mismatch, and block ARB/NV subgroup spellings 2026-07-20 02:36:26 -04:00
swung0x48 293f64b3c2 [Fix] (MG_Impl, DirectGLES): correct ARB_clear_texture error codes, reject cube maps in CopyTextureSubImage2D, advertise the extension on Espryt, and pin the error contracts with tests 2026-07-20 02:36:25 -04:00
swung0x48 e5388c0e7e [Fix] (MG_Backend, MG_Impl, ShaderTranspiler, MG_Test): support iterationRP custom images and storage format reinterpretation 2026-07-20 02:35:30 -04:00
swung0x48 d4922cb0fb [Fix] (MG_Backend, MG_Impl/GLImpl, MG_Util): make anisotropic filtering actually reachable - advertise GL_EXT/ARB_texture_filter_anisotropic only where the host driver or the samplerAnisotropy device feature supports it, answer GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT from the backend limit, and honor the sampler state on DirectVulkan (feature enable, limit clamp, LINEAR-only gate, resolved value in the sampler cache key) 2026-07-16 22:59:11 -04:00
swung0x48 176d130f09 [Fix] (MG_Backend): GetTexImage level-range check was off-by-one (max level is inclusive; single-level textures asserted on level 0), demoted to logged skip; advertise GL_ARB_texture_storage_multisample (entry points already implemented - unadvertised extension left null glw pointers and CTS framebuffer_blit jumped to address 0) 2026-07-16 03:06:03 -04:00
swung0x48 8c89b1618a [Feat] (MG_Backend, android-plugin): show format capability tables in POST 2026-07-16 00:10:11 -04:00
swung0x48 dc3c2cc5c7 [Feat] (MG_Impl, MG_Backend, MG_State): implement glMultiDrawArrays and glGetBufferSubData
Two previously-stubbed GL 3.3 Core entry points.

glMultiDrawArrays: mirrors the existing glMultiDrawElements(BaseVertex)
architecture end to end -- a new MultiDrawArrays backend function-table
slot dispatched from the frontend after program/primitive-mode validation
(plus a drawcount < 0 -> GL_INVALID_VALUE guard).
- DirectGLES: PrepareForDraw once, then loop native glDrawArrays with the
  same per-range client-side array upload the single DrawArrays does.
- DirectVulkan: build a MultiDrawCmd payload and hand it to a new
  VulkanRenderer::MultiDrawArrays, which does one SetupDraw over the union
  of the sub-draw vertex ranges and then a vkCmdDraw per range (mirrors
  VulkanRenderer::MultiDrawElements).

glGetBufferSubData: reads a range of the bound buffer's CPU shadow into
client memory via a new BufferObject::DownloadSubData, with the same
validation shape as BufferSubData (INVALID_VALUE for negative/overflowing
range, INVALID_OPERATION for no bound buffer or a non-persistent mapped
buffer). The shadow reflects CPU writes and backend write-backs but not
arbitrary GPU-side writes, which is documented on the method.

Tests: 2 BufferTest cases for glGetBufferSubData (round-trip read of a
middle range and the whole buffer, plus out-of-range/negative/no-buffer
errors). BufferTest 32/32, SanityTest 30/30, VertexArrayTest 42/42;
library builds clean. (The glMultiDrawArrays draw paths are not
runtime-testable on this host and are compile-verified against the tested
MultiDrawElements pattern.)
2026-07-10 23:46:14 -04:00
swung0x48 f892f609c2 [Fix] (MG_Backend/DirectVulkan+DirectGLES, MG_State): cap per-stage GL_MAX_TEXTURE_IMAGE_UNITS to 32
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.
2026-07-10 21:53:04 -04:00
swung0x48 d40f753983 [Fix] (MG_State, MG_Impl, MG_Backend): conformant current generic vertex attribute values
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).
2026-07-10 11:23:16 -04:00
swung0x48andClaude Fable 5 7b00255b11 [Feat] (MG_Util/SelfTest): grouped POST rows, merged probes, MobileGL-reported strings
Rows in each backend section now sort FAIL -> WARN -> PASS -> INFO
(stable within groups), with identity strings always last: the device
strings renamed to 'Backend driver reported GL_*' and a new bottom
group 'MobileGL reported GL_VENDOR/GL_VERSION/GL_RENDERER/GL_EXTENSIONS'
showing exactly what MobileGL advertises to applications on that
backend, assembled from the same sources as GL_Getter and the backend
objects (extension-list construction extracted into shared helpers so
POST cannot drift from the real advertisement).

Rows probing the same subject are merged into single verdicts whose
details keep every sub-fact and causal chain: the six EGL setup steps
become one 'ES3 context' row, extension presence + functional probe
become one 'Timer queries' row per backend (including the
MOBILEGL_DISABLE_TIMERQUERY override explanation), and the Vulkan
loader/instance, surface-extension pair, and physical-device/queue/API
chains each collapse into one row.

Capability rows previously dumped as INFO now carry verdicts: index
type uint8 (WARN when absent - uint8 index buffers have no conversion
fallback), VK_KHR_draw_indirect_count (WARN when absent - count draws
degrade to CPU readback loops); buffer_storage/base_instance stay
honest INFO when absent since no MobileGL path degrades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 03:28:50 +00:00
swung0x48andClaude Fable 5 7eb3994b02 [Feat] (MG_Impl, MG_Backend): GL_ARB_timer_query on both backends
Implements GL timer queries end to end: a frontend query registry
(modeled on the sync module - mutex-guarded objects wrapping opaque
backend handles behind optional function pointers) serving
glGenQueries/glBeginQuery/glEndQuery(GL_TIME_ELAPSED)/glQueryCounter
(GL_TIMESTAMP)/glGetQueryObject*/glGetQueryiv with GL 3.3 error
semantics and a graceful zero-result fallback when a backend cannot
time.

DirectGLES backs spans with GL_EXT_disjoint_timer_query (context-
generation-stamped handles, bounded result waits). DirectVulkan gets a
VkTimerQueryManager: per-frame-in-flight timestamp query pools reset at
command-buffer begin (outside render passes), records harvested by
frame serial before their pool recycles, elapsed = masked tick delta x
timestampPeriod; handles are stamped with a renderer generation that
also now guards fence syncs across renderer recreation. GL_QUERY_
COUNTER_BITS reports 0 unless the live backend can actually time
(dynamic IsTimerQuerySupported hook), and a failed blocking read keeps
the handle alive so the real value stays reachable once the frame
submits.

GL_ARB_timer_query is advertised only when the device supports timing
and MOBILEGL_DISABLE_TIMERQUERY is unset - LWJGL keys Minecraft's F3
'GPU: x%' line off exactly that extension string; verified on device
(Adreno 830) on both backends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 01:08:38 +00:00
swung0x48andClaude Fable 5 a6a5edf573 [Perf] (MG_Backend/DirectGLES, MG_Impl): cache link-time lookups, bound unit scans, honor eglSwapInterval
- 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>
2026-07-09 12:12:51 +00:00
swung0x48andClaude Fable 5 139de76347 [Fix] (MG_State/MG_Impl/MG_Backend): render Flywheel instanced+indirect on both backends
Create 6 / Flywheel 1.0.6 now renders correctly with both flywheel:instancing
and flywheel:indirect on DirectGLES and DirectVulkan (verified in-game on
Adreno 830: waterwheels and cogwheels solid, animated, correct pairing, no
crashes across all four combinations).

- MG_State/MG_Impl: sync explicitly-ranged SSBO bindings of FLUSH_EXPLICIT
  persistent maps to the backend before compute dispatches. Flywheel writes
  its scatter-copy descriptors into the staging ring's persistent map and
  never flushes that span (UB per spec, works on drivers whose maps alias
  GPU-visible memory); our maps alias the CPU shadow, so the descriptors
  never reached the GPU: the scatter compute copied nothing (GLES: empty
  draw commands) or stale garbage (Vulkan: wild indirect commands ending in
  VK_ERROR_DEVICE_LOST).
- MG_Impl/MG_Backend: real glFenceSync objects backed by backend fences
  (GLES: native ES syncs guarded by context generation and owner thread;
  Vulkan: buffer-manager frame serials), replacing always-signaled stubs
  that let Flywheel reclaim staging memory the GPU still reads.
- MG_Backend/DirectGLES: compute dispatches now run the same per-program
  resource sync as draws (uniform-block bindings and sampler units must be
  re-established through the API because layout(binding) is stripped from
  transpiled ESSL) and rebind texture units afterwards; the cull shader
  used to read a stale _FlwFrameUniforms binding and the depth-pyramid
  downsample sampled a stale unit-0 texture, zeroing the Hi-Z pyramid and
  occlusion-culling all Flywheel geometry. Image uniforms are excluded from
  glUniform1i (ES bakes their unit via layout(binding)); image-unit sync is
  clamped to the device limit; eliminated/SSBO-classified uniform blocks
  are skipped.
- MG_Backend/DirectGLES: gl_BaseInstance in native indirect draws reads the
  GPU-written command buffer through an injected mg_IndirectParams SSBO
  view addressed per draw instead of the zero CPU shadow; layout(binding)
  is preserved for SSBO/image declarations (ES has no API rebinding for
  them); the ES context ownership claim moved to a global atomic owner
  thread with an EGL ground-truth check, and deferred buffer op state is
  mutex-guarded, so ops cannot silently no-op after context migration.
- MG_Backend/DirectVulkan: new RebaseInstanceIndexPass rewrites vertex
  InstanceIndex loads to (InstanceIndex - BaseInstance). glslang's relaxed
  Vulkan mode aliases gl_InstanceID to InstanceIndex, which includes
  firstInstance, but GL's gl_InstanceID is zero-based - draws with nonzero
  baseInstance paired meshes with wrong instance data (cogwheel drawn as a
  waterwheel, another wheel collapsed invisible). Gated on the
  shaderDrawParameters device feature. Sampled-read barriers additionally
  cover the compute stage (the Hi-Z downsample samples the depth
  attachment from compute), and short uniform-buffer ranges keep the
  existing zero-padding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 06:30:10 +00:00
swung0x48andClaude Fable 5 5ea49f3c50 [Feat] (MG_Backend/DirectGLES): support Flywheel indirect rendering
- Advertise ARB_gpu_shader5 / ARB_multi_bind / ARB_shading_language_420pack /
  ARB_vertex_attrib_binding / ARB_shader_image_size so LWJGL reports
  SUPPORTS_INDIRECT.
- New LowerDrawParametersPass demotes DrawIndex/BaseInstance/BaseVertex
  builtins to Private globals (mg_DrawID/mg_BaseInstance/mg_BaseVertex) for
  the ESSL transpile; SPIRV-Cross otherwise throws for ES profiles. The
  program manager promotes the emitted globals to uniforms and feeds them
  per (sub-)draw.
- Indirect draws now execute natively on the GPU (glDrawElementsIndirect /
  glDrawArraysIndirect per command) when an indirect buffer is bound, so
  compute-written command fields (Flywheel culling updates instanceCount)
  are honored; detects GL_EXT_base_instance and falls back to the CPU loop
  when the command's baseInstance cannot be consumed natively.
- Sync SSBO binding points for graphics draws, not just compute (Flywheel
  vertex shaders read instance data from SSBOs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:33:05 +00:00
swung0x48andClaude Fable 5 b8ffd25148 [WIP] Mesa-style buffer overhaul: resource abstraction + immediate transfer ops
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 14:47:51 +08:00
swung0x48 0c8af978db [Chore] (MG_Backend): lower format capability logs 2026-07-07 09:58:30 +08:00
swung0x48 6418561d3c [Fix] (MG_State/EGLState, MG_Backend): defer current EGL surface destruction 2026-07-03 10:14:26 +08:00
swung0x48 51d9fa91ed [Fix] (MG_Impl/GLImpl, MG_Backend): fix piglit texture and buffer cases 2026-07-03 08:51:22 +08:00
swung0x48 5cdc6c902e [Fix] (MG_Impl/EGLImpl, MG_Backend): track EGL surface lifecycle 2026-07-03 07:45:40 +08:00
swung0x48 5b116696f0 [Fix] (MG_Impl/EGLImpl, MG_Backend): handle EGL current handoff for NeoForge earlydisplay
Treat valid-display no-surface eglMakeCurrent calls as EGL release requests, keep EGLState and backend current records consistent across threads, and rebind the native DirectGLES EGL context during attach/release.

Add EGLState coverage for cross-thread owner transfer and same-thread release/reattach behavior.
2026-06-30 16:44:38 +08:00
swung0x48 2f1949e093 [Feat] (iOS): support MobileGL builds and Metal surfaces 2026-06-30 07:25:20 +08:00
swung0x48 5d6cb7dfed [Fix] (MG_Backend/DirectGLES): fix Super Duper Vanilla on Adreno
- emulate RGB16 SNORM fallback writes
2026-06-27 00:06:13 +08:00
swung0x48 195330ccea [Fix] (MG_Backend/DirectGLES): fix Complementary shaders on Mali
- emulate RGBA8 SNORM fallback writes
2026-06-26 23:33:46 +08:00
swung0x48 943600edb2 [Feature] (MG_Backend): log format caveat fallbacks 2026-06-26 14:49:51 +08:00
swung0x48 76acae9889 [Fix] (MG_Backend/DirectGLES): make format caveats probe-driven 2026-06-26 14:12:24 +08:00
swung0x48 9cca0a8753 [Feature] (MG_Backend): print format capability tables 2026-06-26 10:44:55 +08:00
swung0x48 496fa50a23 [Fix] (MG_Impl/GLImpl): report backend format capabilities 2026-06-25 23:25:31 +08:00
swung0x48 50eda634e0 [Fix]: fix iterationT
- implement glCopyImageSubData frontend export and backend dispatch

- add DirectGLES depth-only CopyImageSubData via framebuffer depth blit

- add DirectGLES R32F CopyImageSubData fallback for GLES drivers rejecting native copy

- allocate DirectGLES generated mipmap storage for depth-only and R11FG11FB10F manual generation

- generate DirectGLES depth-only mipmaps with explicit depth blits

- generate DirectGLES R11FG11FB10F mipmaps with explicit color blits

- add DirectVulkan CopyImageSubData with explicit image copy and layout transitions

- use native Vulkan blit for depth-only mipmap generation

- remove unused Vulkan depth mipmap shader fallback path
2026-06-19 08:14:57 +08:00