Commit Graph
272 Commits
Author SHA1 Message Date
swung0x48 bc2d698b3e [Fix] (DirectGLES): apply the read buffer when one FBO is bound as both draw and read
- SyncCurrentFBO skips the READ-target pass when the same GL FBO is bound as
  both draw and read (the common GL_FRAMEBUFFER case), but the read buffer
  (glReadBuffer) is only applied inside SyncToBackend's READ path — so the skip
  silently dropped every glReadBuffer change, leaving the backend read buffer
  stuck at COLOR_ATTACHMENT0.
- Extract the read-buffer application into BackendFramebufferObject::
  SyncReadBufferToBackend and invoke it from the skip branch (target == Read)
  as well as from SyncToBackend, so reads always target the right attachment.
- Bind the backend FBO as READ inside the helper before glReadBuffer, since the
  skip path only bound it as DRAW.
- Fixes KHR-GL3x.draw_buffers.draw_buffers_1 (reading COLOR_ATTACHMENT1 while
  the FBO stays GL_FRAMEBUFFER-bound returned attachment 0's value); the render
  was already correct, only the readback resolved the wrong attachment.
2026-07-21 12:18:55 -04:00
swung0x48 2a0ae743a0 [Fix] (DirectGLES): glFinish before the glGetTexImage temp-FBO readback
- Mali (tile-based) does not resolve a texture's render into memory when it is read back through a different (temp) FBO than the one it was rendered with
- The cross-FBO glReadPixels raced the deferred tile resolve and returned pre-render clear contents
- Distinct render targets read back byte-identical, so KHR-GLxx.glsl_noperspective failed on Mali-G715 (all four programs read as the clear colour)
- glGetTexImage is already a CPU/GPU sync point so the extra drain is negligible; Adreno resolves eagerly and was unaffected
2026-07-21 03:34:33 -04:00
swung0x48 1929a7c546 [Fix] (MG_State, MG_Backend/DirectGLES): preserve legacy texture reuse and clear default backend bindings - keep generated-but-unbound names alive for Minecraft 1.7.10 atlas uploads, synchronize bind-0 to native GLES without 1D/2D alias churn, and cover both paths with BGRA sub-image and binding-cache regressions 2026-07-17 21:32:15 -04:00
swung0x48 a25d8ee0e7 [Fix] (MG_Backend/DirectGLES): GLES clamps glClearColor to [0,1], zeroing the -FLT_MAX MAX-blend identity; route out-of-range color clears through glClearBufferfv 2026-07-17 11:45:55 -04:00
swung0x48 c30bd0fabb [Fix] (MG_Backend/DirectGLES, MG_Impl/GLImpl): draw/read-buffer state could land on the wrong FBO (glDrawBuffer's static-array latch; SyncToBackend emitting glDrawBuffers/glReadBuffer for the non-bound target; no resync on bound-FBO attachment/drawbuffer edits) leaving MC 26.3's OIT color clears as no-ops; apply per bound target and track the FBO object version 2026-07-17 11:45:54 -04:00
swung0x48 c9fbf79d6a [Fix] (MG_Backend/DirectGLES): blend equations were never synced to the driver (OIT's GL_MAX ran as ADD); diff and emit glBlendEquationSeparate(i) alongside the factor sync 2026-07-17 11:45:53 -04:00
swung0x48 f39e8738da Merge origin/dev (readback overhaul c6d22e6e) into default-texture-objects - true per-target default texture objects supersede the readback branch texture-0 silent no-ops: removed the null-slot early-outs in TexImage1D/2D/3D(Multisample) and TexBuffer plus the DefaultTextureOperationsAreSilentNoOps test so name-0 operations actually (re)specify the default objects; deduped the shared state-reset fixes, keeping upstream std::clamp for GL_MAX_UNIFORM_BUFFER_BINDINGS, the Int-typed ActiveTexture combined-units range check, renderbuffer name-0 unbind, and vertex-attrib-0 current-value writes with the attrib-0 round-trip test 2026-07-16 23:50:47 -04:00
swung0x48 076cd0d19d [Feat] (MG_State, MG_Impl/GLImpl): per-target default texture objects (name 0) - binding 0 binds a real per-context default object (the initial binding of every unit/target slot, rebound on delete of a bound texture), so glTexImage*/glTexParameter*/glGetTex* on it work like any texture while glIsTexture(0)/Gen/Delete keep excluding it and TexStorage* rejects it per spec; backends skip image-less defaults as cheaply as the old null slots (DirectGLES per-draw sync/bind loops, DirectVulkan sampler-fallback resolve); also accept the full advertised GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS range in glActiveTexture, allow zero-layer TexImage3DMultisample, and let glTexBuffer(buffer=0) detach - the texture section of GL CTS per-case state reset (gluStateReset) now runs clean 2026-07-16 23:36:43 -04:00
swung0x48 870d882fef [Fix] (MG_Backend/DirectGLES, MG_Impl/GLImpl, MG_Util): GL CTS packed_pixels + texture_swizzle readback overhaul - canonical shadow layouts for legacy sized/unsized/packed internal formats (RGB5->RGB565, RGB10/12->RGB16, RGBA2->RGBA4, RGB10_A2(UI)/RGB9_E5/R11F_G11F_B10F packed-word shadows with per-texel encode/decode incl. 5_9_9_9_REV and 10F_11F_11F_REV client types), GL_UNSIGNED_INT_10_10_10_2 pixel type mapping, conversion-first GetTexImage with CPU-shadow fallback for non-attachable formats and stale-temp-FBO detach, narrow implementation read pairs + SNORM read candidates + 2_10_10_10_REV wide-read decode with RGBA expansion, PACK image/skip and SWAP_BYTES honored on the CPU repack (never in ES), state-reset conformance (default-texture TexParameter/TexImage/TexBuffer no-ops, renderbuffer 0 unbind, vertex attrib 0 current value, ActiveTexture up to combined units, UBO binding count clamp), FramebufferTexture3D/TextureLayer slice attachments via glFramebufferTextureLayer, capability-driven FBO UNSUPPORTED for non-renderable colors, ReadPixels integer-ness mismatch error, single-value texture swizzle validation, and DirectGLES 1D/1D-array/2D-array texture emulation (2D/2D-array backend targets matching SPIRV-Cross ES 1D-as-2D shaders) 2026-07-16 22:41:54 -04:00
swung0x48 b831dae8d5 [Feat] (MG_Backend/DirectGLES): packed-type readback encoding - repack wide RGBA reads into all GL 3.3 packed pixel types (3_3_2/2_3_3_REV, 5_6_5(_REV), 4_4_4_4(_REV), 5_5_5_1/1_5_5_5_REV, 8_8_8_8(_REV), 10_10_10_2/2_10_10_10_REV, packed-float 10F_11F_11F_REV and shared-exponent 5_9_9_9_REV) for ReadPixels/GetTexImage; conversion helpers extracted to context-free ReadbackImpl (Utils.cpp) with unit tests asserting exact packed words against the GL CTS pack_* oracle layouts 2026-07-16 11:08:02 -04:00
swung0x48 0005a50517 [Feat] (MG_Backend/DirectGLES): packed-type readback encoding - repack wide RGBA reads into all GL 3.3 packed pixel types (3_3_2/2_3_3_REV, 5_6_5(_REV), 4_4_4_4(_REV), 5_5_5_1/1_5_5_5_REV, 8_8_8_8(_REV), 10_10_10_2/2_10_10_10_REV, packed-float 10F_11F_11F_REV and shared-exponent 5_9_9_9_REV) for ReadPixels/GetTexImage; conversion helpers extracted to context-free ReadbackImpl (Utils.cpp) with unit tests asserting exact packed words against the GL CTS pack_* oracle layouts 2026-07-16 11:08:02 -04:00
swung0x48 6ca48e40fe [Feat] (MG_State, MG_Util, MG_Impl/GLImpl, MG_Backend/DirectGLES): desktop-GL single-channel client formats GL_GREEN/GL_BLUE/GL_ALPHA and _INTEGER variants - validate and readback via wide-RGBA channel extraction (GL CTS packed_pixels rgba8_format_green/blue read with them), unpack GREEN/BLUE(_INTEGER) TexImage uploads into the named channel with 0/1 defaults per table 3.3; GL_ALPHA keeps the legacy Red upload mapping (R8 storage + 000R swizzle), its readback corrected at the backend to source channel 3 2026-07-16 06:47:33 -04:00
swung0x48 1cefb9780b [Feat] (MG_State, MG_Util, MG_Impl/GLImpl, MG_Backend/DirectGLES): desktop-GL single-channel client formats GL_GREEN/GL_BLUE/GL_ALPHA and _INTEGER variants - validate and readback via wide-RGBA channel extraction (GL CTS packed_pixels rgba8_format_green/blue read with them), unpack GREEN/BLUE(_INTEGER) TexImage uploads into the named channel with 0/1 defaults per table 3.3; GL_ALPHA keeps the legacy Red upload mapping (R8 storage + 000R swizzle), its readback corrected at the backend to source channel 3 2026-07-16 06:47:33 -04:00
swung0x48 a1a8a18575 [Fix] (MG_Backend/DirectGLES, MG_Impl/GLImpl): ReadPixels - fall back to wide-format conversion when the ES driver rejects a legacy native read combo (Adreno errors on e.g. GL_RED/GL_UNSIGNED_INT and leaves the buffer untouched), and enforce packed-type/format pairing at the state layer via shared ValidateClientFormatTypePairing (GL_RED + GL_UNSIGNED_SHORT_5_6_5 now raises GL_INVALID_OPERATION) 2026-07-16 06:11:53 -04:00
swung0x48 bae222227a [Fix] (MG_Backend/DirectGLES, MG_Impl/GLImpl): ReadPixels - fall back to wide-format conversion when the ES driver rejects a legacy native read combo (Adreno errors on e.g. GL_RED/GL_UNSIGNED_INT and leaves the buffer untouched), and enforce packed-type/format pairing at the state layer via shared ValidateClientFormatTypePairing (GL_RED + GL_UNSIGNED_SHORT_5_6_5 now raises GL_INVALID_OPERATION) 2026-07-16 06:11:53 -04:00
swung0x48 b8dc4a6004 [Fix] (MG_Util/Texture): expand channels and convert component types on texture unpack to the internal shadow layout 2026-07-16 04:39:57 -04:00
swung0x48 20e1b417cc [Fix] (MG_Util/Texture): expand channels and convert component types on texture unpack to the internal shadow layout 2026-07-16 04:39:57 -04:00
swung0x48 8496e7c7eb [Fix] (MG_Backend/DirectGLES): convert narrow client formats (RED/RG/RGB/BGR/BGRA + integer variants, byte/short/half/8888 types) in GetTexImage/ReadPixels via wide RGBA readback 2026-07-16 03:40:54 -04:00
swung0x48 f0ed5c1b8e [Fix] (MG_Backend/DirectGLES): convert narrow client formats (RED/RG/RGB/BGR/BGRA + integer variants, byte/short/half/8888 types) in GetTexImage/ReadPixels via wide RGBA readback 2026-07-16 03:40:54 -04:00
swung0x48 5b38f61961 [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 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 273c7ebcf0 [Fix] (MG_Impl/GLImpl, MG_State, MG_Backend/DirectGLES): eliminate packed_pixels SIGTRAPs - complete TexImage format/type/internalformat validation matrix (depth-stencil family, integer-ness, packed-type pairing, 3D depth rejection), fix inverted UpdateSubData assert with clamped copy, demote unimplemented readback asserts to logged no-ops 2026-07-16 02:23:18 -04:00
swung0x48 fe7a5ee1b2 [Fix] (MG_Impl/GLImpl, MG_State, MG_Backend/DirectGLES): eliminate packed_pixels SIGTRAPs - complete TexImage format/type/internalformat validation matrix (depth-stencil family, integer-ness, packed-type pairing, 3D depth rejection), fix inverted UpdateSubData assert with clamped copy, demote unimplemented readback asserts to logged no-ops 2026-07-16 02:23:18 -04:00
swung0x48 315e9cb194 [Perf] (MG_Backend/DirectGLES): replace per-draw global-UBO glBufferSubData with a persistent-mapped ring allocator (fence-watermark reclaimed, MOBILEGL_DISABLE_UBO_RING opt-out); scrub stale indexed-binding shadow on glDeleteBuffers 2026-07-16 00:10:12 -04:00
swung0x48 2e7073a890 [Perf] (MG_Backend/DirectGLES): replace per-draw global-UBO glBufferSubData with a persistent-mapped ring allocator (fence-watermark reclaimed, MOBILEGL_DISABLE_UBO_RING opt-out); scrub stale indexed-binding shadow on glDeleteBuffers 2026-07-16 00:10:12 -04:00
swung0x48 a26e9aaf25 [Refactor] (MG_Config, MG_Backend, trace-replay): remove unused stats instrumentation 2026-07-13 20:33:31 -04:00
swung0x48 ad9ee99521 [Perf] (MG_Backend/DirectGLES): dedup per-draw indexed UBO/SSBO binds with a shadow cache; BindCurrentProgramWithResources 5.1% -> 3.1% 2026-07-12 06:32:18 -04:00
swung0x48 d7029952bb [Perf] (MG_Backend/DirectGLES): recycle idle GL buffers via a fence-gated size pool instead of glDeleteBuffers; pinned fps 184->220 2026-07-12 05:28:34 -04:00
swung0x48 340449b77e [Perf] (MG_State, MG_Backend/DirectGLES): skip never-touched buffer bind points via high-water mark; SyncNeccessaryBuffers 15.7% -> 4.2% 2026-07-12 04:31:34 -04:00
swung0x48 527e229ac8 [Perf] (MG_Backend/DirectGLES): drop redundant per-draw UBO binding-point sync; BindCurrentProgramWithResources already rebinds them 2026-07-12 04:11:23 -04:00
swung0x48 c1743aa42d [Refactor] (MG_State, MG_Backend): PipeResource storage layer + zero-copy coherent persistent maps
Introduce a Mesa pipe_resource-style PipeResource that owns a GL buffer's bytes
and its backend GPU resource, abstracting WHERE the authoritative bytes live:
 - Shadow mode (non-persistent buffers): a CPU Vector; the backend keeps its own
   GPU copy in sync via BufferBackendOps, exactly as before.
 - Persistent mode (coherent GL_MAP_PERSISTENT maps): the backend's host-visible,
   COHERENT, persistently-mapped GPU memory is the single source of truth. The app
   writes into it directly, every reader resolves against it, and NO per-write
   backend transfer happens. The CPU shadow is released.

BufferObject no longer owns a raw shadow Vector; it holds a PipeResource and
exposes one accessor, MappedData(), that all readers go through. Every buffer-data
consumer (UBO payload, PBO texture upload, indirect draws, resident/streamed
uploads, both backends) was migrated from GetDataReadOnly()->data() to
MappedData(), so a persistent buffer's readers see GPU memory - not a stale
shadow. That stale-shadow inconsistency is what corrupted rendering (wrong UBOs ->
misplaced/"lost" vertices) in the first zero-copy attempt (625c8a6, reverted in
896cafc); routing every consumer through one accessor makes it structurally
impossible.

Backends provide the map via BufferBackendOps::AcquirePersistentMap:
 - DirectVulkan: a HOST_VISIBLE|HOST_COHERENT (required, not just requested),
   persistently mapped resident VkBuffer carrying every usage, seeded from the
   shadow, never recreated; AcquireResidentSlice binds it directly.
 - DirectGLES: EXT_buffer_storage immutable persistent+coherent glMapBufferRange,
   falling back to the shadow when the extension is absent.

Fixes the ~7GB GpuMemory OOM + 100%-CPU/ANR running modern Blaze3D Minecraft on
both Magma and Espryt (per-draw whole-buffer re-upload of the coherent persistent
ring buffer), without the coherency/stale-read hazards of the reverted attempt.

BufferTest: zero-copy stress guard (15,360 draws -> 0 per-draw transfers, and every
reader resolves to GPU memory) + a shadow-fallback test. Host suite: 203/203 pass.
Device verification pending.
2026-07-11 20:18:38 -04:00
swung0x48 0f99d93300 [Feat] (MobileGL): full dual-source blending across state, transpiler, and both backends
Wire GL_SRC1_* dual-source blend factors (glBlendFunc) end to end with the
glBindFragDataLocationIndexed color index, so a fragment shader can drive both
dual-source blend inputs.

State + converters:
  - RenderState BlendFactor gains Src1Color/OneMinusSrc1Color/Src1Alpha/
    OneMinusSrc1Alpha; GLToMG/MGToGL/MGToVk/MGToStr converters map them to
    GL_SRC1_*, VK_BLEND_FACTOR_SRC1_*, and readable names.

Transpiler layout(index = N):
  - ProgramAttrib carries explicitFragmentOutIndices; ProgramObject threads
    m_explicitFragDataIndex into it at both link sites.
  - TMglGlslIoResolver applies the color index as TQualifier.layoutIndex on the
    fragment output, emitting layout(index = 1) via the glslang Index decoration
    -> SPIRV-Cross path. Only the non-zero (dual-source) index is emitted: index 0
    is the GL default and an explicit "index = 0" would demand
    GL_EXT_blend_func_extended on GLES for ordinary single-source outputs.

Feature detection, POST, and hard-fail at use time (no silent fallback):
  - Vulkan: dualSrcBlend is detected at device creation and cached; a draw whose
    enabled blend state uses a SRC1 factor without the feature throws at pipeline
    build with the reason and a pointer to the POST row.
  - GLES: GL_EXT_blend_func_extended detected at load into
    GLESCapabilities.SupportsDualSourceBlend; a draw enabling blend with a SRC1
    factor without it throws in the blend-state sync with the same guidance.
  - DriverPost adds a dual-source-blend row for both backends (Pass/Warn).

Tests:
  - ProgramTest.CompileAndLinkWithExplicitFragmentOut now asserts the transpiled
    fragment shader carries layout(location = 0, index = 1) after a re-link with
    glBindFragDataLocationIndexed(index 1), and still omits any index qualifier
    for the plain index-0 output.
2026-07-11 01:32:07 -04:00
swung0x48 e9fa99e16b [Feat] (MG_Backend): wire primitive restart into both backends; detect dualSrcBlend
Make GL_PRIMITIVE_RESTART[_FIXED_INDEX] actually take effect at draw time,
following the detect-at-init / POST / fallback-or-hard-fail discipline.

DirectVulkan:
- Thread primitiveRestartEnable through the pipeline (payload + hash +
  input-assembly), set from the GL_PRIMITIVE_RESTART / _FIXED_INDEX caps.
- Detect and enable primitiveTopologyListRestart
  (VK_EXT_primitive_topology_list_restart) at device creation; cache it.
  Strip/fan restart needs no feature; a *list* topology with restart and
  no feature hard-fails at the draw with the reason.
- Vulkan only restarts on the fixed all-ones index value, so an arbitrary
  GL_PRIMITIVE_RESTART index that is not that value hard-fails in
  UploadAndBindIndexBuffer (where the index type is known).
- Also detect+enable and cache the dualSrcBlend base feature (groundwork
  for GL_SRC1_* dual-source blending).

DirectGLES:
- Sync GL_PRIMITIVE_RESTART_FIXED_INDEX from either restart cap (GLES core
  has only the fixed-index form); an arbitrary non-fixed index hard-fails
  in the indexed draw paths with the reason.

POST: dualSrcBlend and primitiveTopologyListRestart capability rows (Pass
when supported, Warn with the fallback/hard-fail consequence otherwise).

Library builds clean; SanityTest 31/31. (The actual restart rendering and
the hard-fail paths need a real GPU and are not runtime-testable here.)
2026-07-11 01:11:23 -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 041de6cba3 [Feat] (MG_Backend/DirectGLES, MG_Util/Loader): wire glPolygonMode and glColorMaski into GLES sync
Neither entry point exists in unextended OpenGL ES core, so both are
gated on optional extensions detected and cached at init, with a runtime
fallback when absent.

Loader:
- Add glPolygonModeNV/glPolygonModeANGLE and glColorMaskiEXT/glColorMaskiOES
  to the GLES function table, loaded via a new INIT_GLES_FUNC_OPTIONAL
  macro that does not log an error when the driver lacks them.
- Cache GLESCapabilities.SupportsPolygonMode and SupportsIndexedColorMask
  from whether the entry points loaded (glColorMaski is GLES 3.2 core with
  no extension string, so pointer presence is the reliable signal).

Sync (SyncRenderState):
- Color mask: uniform masks keep using the non-indexed glColorMask (works
  everywhere); divergent per-draw-buffer masks use glColorMaski (core /
  EXT / OES, whichever loaded) when SupportsIndexedColorMask, else fall
  back to broadcasting draw buffer 0. Mirrors the existing indexed-blend
  block's all-same-vs-per-buffer structure.
- Polygon mode: new sync block calls glPolygonModeNV/ANGLE(GL_FRONT_AND_BACK,
  mode) when SupportsPolygonMode; without the extension the mode stays FILL
  and non-FILL requests are dropped.

Library builds clean; full SanityTest sweep green (30/30).
2026-07-10 22:27:21 -04:00
swung0x48 5e8106114f [Feat] (MG_Impl/GLImpl, MG_State, MG_Backend): implement glColorMaski
Promote the color writemask to per-draw-buffer state and implement the
indexed glColorMaski entry point (previously a stub), plus its read-back
through glGetBooleani_v.

- RenderState: replace the single BoolVec4 ColorMask with an array of
  MAX_DRAW_BUFFERS masks, all initialized to true. SetColorMask now
  broadcasts to every draw buffer (glColorMask semantics); GetColorMask
  returns draw buffer 0. Add indexed set/get accessors + GLContext
  wrappers.
- glColorMaski sets only the addressed draw buffer; out-of-range index
  raises GL_INVALID_VALUE (buf is a GLuint, so no GL_INVALID_ENUM path),
  mirroring the indexed blend entry points' MAX_DRAW_BUFFERS bound.
- glGetBooleani_v(GL_COLOR_WRITEMASK, i) reports draw buffer i's four
  booleans; the non-indexed glGetBooleanv still reports draw buffer 0.
- Fix GLboolean coercion in the color-mask path: any nonzero value
  enables the component (was == GL_TRUE, which wrongly rejected e.g. 2).
- DirectGLES sync reads ColorMasks[0] (GLES core has only non-indexed
  glColorMask).

Tests: ColorMaskIndexedStoresAndReadsBack covers the per-buffer vs
broadcast semantics, buffer-0 read-back, out-of-range INVALID_VALUE, and
the GLboolean coercion (mutation-verified: == GL_TRUE fails it). Full
SanityTest sweep green (30/30).
2026-07-10 21:30:57 -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 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 85b68a9969 [Fix] (MG_Backend/DirectGLES): rebase gl_InstanceID for native indirect draws on ANGLE
ES keeps gl_InstanceID zero-based and ignores the indirect command's
'reserved, must be zero' word, but ANGLE-on-Vulkan forwards the command
verbatim to vkCmdDraw*Indirect and compiles gl_InstanceID to SPIR-V
InstanceIndex, which includes firstInstance. Shaders computing
gl_BaseInstance + gl_InstanceID (Flywheel indirect) then add the base
twice, scrambling instance-to-mesh association.

Probe the actual driver semantics at capability-fill time with a tiny
indirect draw (an ES indirect draw needs a non-default VAO) and, on
leaking drivers, rewrite vertex shaders that use the native indirect
SSBO machinery so gl_InstanceID subtracts the command's baseInstance
word during native indirect draws.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:14:02 +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 2395a6ded2 [Fix] (MG_Backend/DirectGLES): always use native indirect draws with a bound buffer
Adreno (830) exposes no GL_EXT_base_instance, and gating the native path
on it sent Flywheel's whole MDI call to the CPU loop, which reads the
stale shadow instanceCount (0) and draws nothing. A non-zero reserved
word is benign on mobile drivers, instanced arrays were never
baseInstance-offset in the emulation anyway, and the CPU loop can never
see GPU-written commands - native is strictly better.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:47:45 +00:00
swung0x48andClaude Fable 5 07055bb531 [Fix] (MG_Backend/DirectGLES): bind GL_DRAW_INDIRECT_BUFFER for native indirect draws
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:34:31 +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 f355080b6f [Fix] (MG_Backend/DirectGLES): survive ES context recreation in buffer ops
Track context generation + synced change serial per resource; re-register
ops on MakeCurrent. Fixes frozen buffer contents after the trace replayer's
probe context teardown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:02:38 +08: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 616e694bdd [Fix] (MG_Backend/DirectGLES): flush ANGLE memory barriers 2026-07-06 12:39:51 +08:00
swung0x48 93cf3559e1 [Fix] (MG_Backend/DirectGLES): fix Photon image replay 2026-07-05 18:41:32 +08:00
swung0x48 940ab5fd8e [Fix] (MG_State/GLState, MG_Backend): track layered framebuffer textures 2026-07-03 10:38:51 +08:00
swung0x48 ce2b5a793f [Fix] (MG_Backend): support GLES depth-stencil readback 2026-07-03 10:02:49 +08:00