mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
f3d52faad415ce6f6b230f3f53762007e79bf65b
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f3d52faad4 |
[Perf] (MG_State, MG_Backend): stop glViewport from evicting a cached VkPipeline
RenderState kept one version counter for all render state, and DirectVulkan read it in three places: the pipeline memo key, the SetupDrawSnapshot fast-path guard, and that guard's store. So glViewport, glScissor, glBlendColor, glStencilMask, glClearColor, glPolygonOffset, glLineWidth and the point-size family - none of which can alter a VkPipeline, all of which an application changes between draws - knocked the next draw off both fast paths and made it rebuild a pipeline lookup that was already correct. The counter is now split. m_version still moves on every state change, because the draw snapshot really does depend on all of it. m_pipelineStateVersion moves only for the state a backend bakes into a pipeline object, and it is what the three DirectVulkan sites read. The exclusion list is the eight VkDynamicState entries PipelineFactory declares plus the state that is not pipeline state at all (the clear values, hints, the point-size family, clamp read colour, the primitive restart index). glStencilFunc is the one setter that had to be split rather than classified: Func is in the pipeline payload but Ref and ValueMask are dynamic state, so it bumps the pipeline version only when Func actually changes. Capabilities are deliberately NOT in the exclusion list even though several look like dynamic state: GL_FRAMEBUFFER_SRGB feeds the render-pass hash, depth and stencil test feed drawUsesDepthStencil, and scissor test, blend, cull face, polygon offset fill, primitive restart, colour logic op and rasterizer discard all feed the pipeline payload. Two smaller draw-path wins ride along, both removing work whose answer was already in hand. UploadAndBindVertexStreams searched all 32 VAO attribute slots for the SharedPtr matching a binding's buffer key, once per binding per draw - but VertexInputStateFactory writes bindingBufferKeys[b] and bindingAttributeLocations[b] from the same loop iteration, one binding per attribute with no merging, so the attribute at that location IS the buffer, by construction. UploadAndBindIndexBuffer round-tripped the element-array buffer's raw pointer back through the GL name table on every indexed draw, costing a map lookup and an atomic refcount pair, when the binding slot's SharedPtr was already in scope forty lines above - where a comment says exactly that about the vertex path. Behaviour-neutral by construction and verified as such: a 13355-case subset of GL30-GL45 covering viewport, scissor, blend, stencil, depth, polygon offset, clear, multisample, cull, logic op, line width and point state, plus the whole direct_state_access suite, is identical before and after on both backends - in the failure list and in the crashed-case set. direct_state_access stays at Espryt 370/371 and Magma 371/371. |
||
|
|
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. |
||
|
|
c8c7b19579 |
[Feat] (MG_Backend, MG_Util): give DirectVulkan GL's provoking vertex
Vulkan's built-in convention is "provoking vertex first"; GL's default is LAST_VERTEX_CONVENTION, and GL derives both flat shading and the transform feedback vertex order from it. DirectVulkan had no way to say so, which is why direct_state_access.queries_functional failed on a value with nothing in its log - the primitives came back counted against a strip recorded in the wrong vertex order. VK_EXT_provoking_vertex is now enabled when present, and the mode is a hashed field of the pipeline payload rather than dynamic state, because it is baked into VkPipelineRasterizationStateCreateInfo: two draws differing only in it must not collide on one cached VkPipeline, or whichever mode built first would stick for the rest of the frame. The pNext is chained only when the mode is not Vulkan's default, so a device without the extension produces a byte-identical VkGraphicsPipelineCreateInfo to before. Two carve-outs, both measured rather than reasoned: A geometry shader already emits its triangles in GL's vertex order, so asking for LAST rotates them a second time and transform_feedback.geometry reads back the wrong vertices. The mode is one pipeline bit and the input-assembler path wants the opposite, so the two cannot both be satisfied: a program that runs a geometry shader and captures transform feedback keeps Vulkan's own convention. That test is read off the program's own shader list, not programObj.rasterizationProducerStage - the latter is filled by the clip-fixup analysis, which does not run for every program and reads Unknown for exactly the programs this guard exists to catch. Both halves are link-time facts folded into programObj.hash, so no pipeline memo can hand back one built for the other mode; keying on IsTransformFeedbackActive() instead would be a live bug, since neither memo key moves on glBeginTransformFeedback. transformFeedbackPreservesProvokingVertex is deliberately not requested. It buys nothing here - the capture order queries_functional needs comes from provokingVertexLast alone - and leaving it off keeps VUID-VkGraphicsPipelineCreateInfo-topology-04884 disarmed, so a TRIANGLE_FAN pipeline may take LAST on any device. The blit pipeline routes through the same selector: it has no flat varying and no capture, but on a device without provokingVertexModePerPipeline a blit left on FIRST inside a render pass whose draws are LAST is an illegal mix. Per the POST rule the new extension gets rows for provokingVertexLast and for the two properties that change what MobileGL can promise. Fixes queries_functional on Magma (370/371). An A/B over a 976-case transform feedback / geometry shader / layered rendering subset of GL30-GL45 is otherwise identical on both backends and additionally takes 14 geometry_shader rendering and layered_rendering cases from failing to passing on Magma. |
||
|
|
0e7692251d |
[Feat] (MG_State, MG_Impl, MG_Util): store a compressed texture image and hand it back
glCompressedTexImage2D rejected every internalformat with GL_INVALID_ENUM, so direct_state_access.textures_get_image threw at its first compressed call and reported InternalError with nothing in the log at all - the uncompressed half of the case had already passed. The compressed bytes are now kept verbatim, in a side-channel beside the texel shadow rather than in place of it. That placement is the load-bearing decision: both backends pair MapMipmapData with GetMipmapByteSize while sizing their copy regions from GetMipmapTexelSize, and DirectGLES additionally divides the byte size by the texel count to recover bytes-per-texel, so putting 16 bytes where a 4x4 RGBA8 extent says 64 would be an out-of-bounds read on both. The texel storage therefore stays uncompressed and correctly sized - the image samples as zeros, which is the same deviation the RGTC/BPTC/ETC2 arms of ConvertGLEnumToTextureInternalFormat already document - while glGetCompressedTexImage returns the image *as stored*, which GL 4.6 core 8.11 requires and which no re-encode could satisfy byte for byte. Nothing ever hands the compressed bytes to GLES or Vulkan, so the shadow is authoritative rather than potentially stale, which is why the readback never asks a backend. The accepted set is exactly the RGTC/BPTC/ETC2-EAC formats core GL requires, and it is deliberately the same set ConvertGLEnumToTextureInternalFormat can back with uncompressed storage, so the upload can never accept a format whose texel shadow it cannot allocate. imageSize is checked against the block arithmetic, which is also what keeps the copy in bounds. Three things the shape depends on. AllocateStorage clears the compressed tag, so a glTexImage2D or glTexStorage2D over the level un-compresses it - without that, textures_compressed_subimage would flip branches and start asking for data MobileGL cannot produce. GL_TEXTURE_COMPRESSED and GL_TEXTURE_COMPRESSED_IMAGE_SIZE are answered per level rather than per texture, because a compressed internalformat handed to glTexImage2D resolves to uncompressed storage and must keep reading as uncompressed. And GL_TEXTURE_INTERNAL_FORMAT now reports the compressed token for such a level, or it would claim GL_RGBA8 while GL_TEXTURE_COMPRESSED said true. Still rejected on purpose: glCompressedTexImage1D/3D and every glCompressedTexSubImage*, which caps the blast radius. Fixes textures_get_image on both backends (Espryt 370/371, Magma 369/371). A/B over a 1210-case compressed/texture-storage/texture-view/buffer-storage subset of KHR-GL45 is identical before and after on both backends but for get_texture_sub_image.errors_test, which stops throwing and fails on a value instead. |
||
|
|
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. |
||
|
|
3b65e646e1 |
[Fix] (MG_Backend): give every colour attachment its own backend slot on DirectGLES
ES only accepts glDrawBuffers bufs[s] == GL_COLOR_ATTACHMENTs, so a desktop glDrawBuffer(GL_COLOR_ATTACHMENT3) cannot be expressed directly and DirectGLES compacts: it physically relocates the draw buffer's image onto backend point 0 so ES's output-0-to-attachment-0 rule lands on the right image. The clears were therefore always correct. The read side was not. GetBackendAttachmentType derived the attachment-to-point map by searching the draw-buffer array and falling back to the identity point for anything it did not find. That derivation is not injective against the compaction: after clearing attachments 0..7 one at a time, every one of them has been relocated onto point 0 in turn, so a later glReadBuffer(GL_COLOR_ATTACHMENT0) - not a draw buffer any more - takes the identity fallback to point 0 and reads attachment 7's image. Hence the single mismatch, 0.875 where 0 was expected: 7/8 is attachment 7's clear colour. The map is now stored state rather than a re-derivation, and kept a permutation: a draw buffer takes the point ES forces on it, everything else keeps its identity point when that point survived, and an attachment evicted from its identity point is parked on the lowest free one so it stays addressable for glReadBuffer and blits. With identity draw buffers nothing moves and not one extra GL call is issued, which is what keeps ordinary rendering untouched. Two things the permutation depends on. The attachment loop now detaches a colour point whose frontend owner is empty - SyncAttachmentObject only ever attaches, so without this a point handed to an empty attachment would still hold the previous owner's image and hand it back. And QueryReadColorAttachmentInternalFormat asked GL_COLOR_ATTACHMENT0 for the format it sizes the multisample-resolve scratch renderbuffer from; it now asks the point the read buffer actually names, since that is only CA0 when the map happens to be identity. Fixes framebuffers_read_draw_buffer on Espryt. A 5677-case readback and framebuffer subset of GL30-33 stays at zero failures on both backends. |
||
|
|
25b9370815 |
[Fix] (MG_Backend): stop a renderbuffer blit reading a freed image layout
VkRenderPassManager kept m_renderbufferResources on FastSTL's open-addressing UnorderedMap while BlitFramebuffer caches a raw pointer into one of its elements - ResolveColorBlitBinding stores &rbResource->layout - and then calls MaterializePendingClearForRenderbuffer, which looks that same resource up again. FastSTL's operator[] runs its load-factor check before find_key and reallocates the whole bucket array when occupancy crosses it, so even a plain lookup relocates every element; erase only tombstones and never lowers the occupancy, so the doubling keeps firing. After a relocation the cached pointer names freed storage still holding the pre-clear VK_IMAGE_LAYOUT_UNDEFINED, BlitFramebuffer takes its "source image layout is undefined" early return, and the blit is silently dropped - glReadPixels then returns the zero-filled fresh allocation. That is why the failures looked arbitrary: which iteration breaks is pure arithmetic on the table's occupancy, and the observed set (GL_R8 at k=0,1,3,7, GL_R16 at k=6, GL_RG16 at k=4) is exactly the doubling ladder. Padding the map with unrelated live renderbuffers moves the failures to the positions the model predicts and every previously failing format then passes, so nothing else hides behind it. Reordering the materialize ahead of the resolves - the fix ReadPixels got, see the note at its call site - does not cover this, because BlitFramebuffer resolves two bindings and the second resolve still runs after the first pointer is taken. The depth blit, GetOrCreateRenderPass's depthRenderbufferResource and ReadDepthStencilPixels cache the same kind of pointer, so the invariant belongs in the container rather than in a per-call-site ordering rule. m_textureResources was already node-based for exactly this reason; this is the map that was left behind. Fixes renderbuffers_storage_multisample on DirectVulkan. |
||
|
|
4ce808b9f2 |
[Feat] (MG_State, MG_Impl, MG_Backend): let a bound program pipeline actually draw
The pipeline object bookkeeping landed already - names, stage slots, queries - but nothing consumed it. Every draw asked the context for the current program, got null because a pipeline is used with program zero, and drew nothing; glCreateShaderProgramv was still a stub returning zero, so direct_state_access.program_pipelines_functional could not even build its stage programs and reported InternalError on both backends. glCreateShaderProgramv is written as the exact call sequence the spec defines it to be, with one deviation that matters: the link goes straight to ProgramObject::Link(false) rather than through LinkProgram, because LinkProgram injects a default fragment shader into a program that has none - correct for a whole program, wrong for a separable vertex-stage one whose fragment stage comes from the pipeline. glDetachShader defers removal to the next link, so the program keeps the shader object it was built from while correctly no longer reporting it attached. GL_PROGRAM_SEPARABLE joins glProgramParameteri and glGetProgramiv. Everything downstream of a draw - both backends, the uniform plumbing, the draw validation - is written against one linked program, so rather than teach all of it about stages, the pipeline is flattened: GetProgramForDraw() composites the stage programs' shaders into a single hidden program object and caches it against a signature of each stage program's lifetime id and link generation, so it is rebuilt exactly when a stage or a stage's link changes. The composite carries no GL name - it must not answer glIsProgram, and it must not consume a name the application could be handed. Uniform entry points get their own resolver rather than sharing that one: glUniform* addresses the pipeline's active program, not the composited draw program. GL_CURRENT_PROGRAM still reads the program in use, which is zero here. Fixes program_pipelines_functional on both backends. |
||
|
|
5545d31c37 |
[Feat] (MG_Backend, MG_Util): give a cube map array real storage on DirectGLES
TextureCubeMapArray was missing from every storage and upload switch in the DirectGLES texture sync, so a cube map array reached the driver with no storage at all - and from the glFramebufferTextureLayer branch, so attaching one of its layers fell through to glFramebufferTexture2D and raised INVALID_ENUM. Every GL_TEXTURE_CUBE_MAP_ARRAY colour check in direct_state_access.framebuffers_texture_layer_attachment read nothing. ES 3.2 has GL_TEXTURE_CUBE_MAP_ARRAY natively and it stores exactly like a 2D array whose depth is six times the cube count, so each switch gains the case beside Texture2DArray and nothing else changes. 1D arrays join the layer branch for the same reason - their backend image is a 2D array. Per the POST rule the new GLES dependency gets a capability (SupportsTextureCubeMapArray, ES 3.2 core or EXT/OES_texture_cube_map_array) and a DriverPost row saying what a user loses without it. Takes framebuffers_texture_layer_attachment from failing to passing on Espryt. It still fails on DirectVulkan, which declines a layered attachment outright. |
||
|
|
588ddba722 |
[Fix] (MG_Backend): scale a depth blit, keep going after one declines, and mip a 1D texture
Three DirectVulkan gaps found together.
glBlitFramebuffer's depth/stencil path refused any blit whose source and
destination extents differ, because vkCmdCopyImage cannot resize. vkCmdBlitImage
can, and VK_FILTER_NEAREST is the only filter Vulkan allows for depth/stencil
anyway - which is what the GL front end already requires. A same-size pair keeps
the cheaper copy.
Worse, that refusal and four others were `return`, not `continue`, so a
depth/stencil aspect this backend could not handle abandoned the whole function -
including the colour blit that only starts after the aspect loop. The CTS's
scaling blits therefore lost their colour as well, which is why
direct_state_access.framebuffers_blit failed all three of its checks rather than
one.
VulkanRenderer::GenerateMipmap declined GL_TEXTURE_1D. It needed nothing else:
the blit loop derives every offset from the storage extent, and a 1D texture's is
{width, 1, 1}, which is exactly the y and z offsets a 1D image requires.
Also: IsTimerQueryResultReady now asks the query pool before the frame serial.
The pool polls with VK_QUERY_RESULT_WITH_AVAILABILITY_BIT and is the authority;
the frame serial only advances at Present and neither completion notifier will
mark the current serial done, so a timestamp written and fence-waited inside one
GL frame could never be read back within it.
Takes framebuffers_blit and textures_generate_mipmaps from failing to passing on
DirectVulkan. queries_functional still fails there on a value.
|
||
|
|
62301b1061 |
[Fix] (MG_State): let a double-typed varying be captured by transform feedback
ResolveXfbSymbolType accepted only float, int and uint, and its caller reports anything it rejects as "Transform feedback varying 'x' is not an output of the vertex stage" - which is a misleading thing to say about a varying that is right there in the shader, just declared `double`. Program linkage failed outright. Doubles are now resolved to the GL_DOUBLE* types, in vector and matrix form, and the per-element size is computed from an 8-byte component rather than a hardcoded 4 (GL 4.6 core 11.1.2.1), so the byte-based limit checks charge a double what GL says it costs. direct_state_access.vertex_arrays_attribute_format stops throwing on both backends and fails on the captured values instead: the capture layout still owes the 8-byte alignment doubles require, and neither backend feeds a 64-bit vertex attribute yet - DirectGLES cannot at all, ESSL having no double. |
||
|
|
f3a846d336 |
[Docs] (README): carry the 4.2 short-term target into the status note
The compatibility section already said 4.2; the status note at the top of the README still said 3.3, so the two disagreed depending on how far a reader got. |
||
|
|
9cdc82fbdd |
[Fix] (MG_Backend): actually bind the sampler object DirectGLES just synced
BindCurrentTextures' program-driven path synced a bound sampler object's parameters to its backend object and then never put it on the texture unit, so every sampler object was inert and the driver kept sampling with the texture's own parameters - direct_state_access.samplers_functional read black where the sampler's NEAREST filtering should have given red. The bind alone is a regression, and the CTS says so loudly: a sampler left on a unit by an earlier draw keeps being applied, and a multisample texture takes no sampler object at all, so the next draw against one is rejected and all 27 textures_storage_multisample_3d_* cases fail. The sibling path in the same function had an empty else branch where the unbind belonged; it now unbinds, making the two symmetric. Takes samplers_functional from failing to passing on Espryt, with no other case moving in either direction. |
||
|
|
4a9d20c49f |
[Fix] (MG_Backend): resolve a framebuffer attachment's layer in the Vulkan blit bindings
ResolveAttachmentBaseArrayLayer answered zero for everything but a cube map face, so every blit, copy and glReadPixels against a layered attachment read layer zero whatever was attached. It reads the attachment's layer now. A 3D texture needs the other half of the distinction: its image has arrayLayers == 1 and the GL layer is a z slice, which VkBufferImageCopy will not take as a base array layer. BlitImageBinding carries it separately as depthOffset, and the readback copy region uses it as the image offset's z. Takes textures_copy from failing to passing on DirectVulkan, which is what glCopyTextureSubImage3D needs to see the slice the CTS attached rather than slice zero. |
||
|
|
394d1ce748 |
[Feat] (MG_Impl, MG_Util): copy into 1D and 3D textures, and accept the BPTC and ETC2 enums
Two unrelated texture gaps. glCopyTextureSubImage1D and 3D validated their arguments and then did nothing: CopyTexSubImage1D_State and CopyTexSubImage3D_State were empty TODOs and no backend exposes anything but a 2D blit. But a texture's contents live in its CPU storage - the backends sync from it - so the copy does not need a blit at all. CopyReadFramebufferIntoMipmapRegion reads the region out of the read framebuffer through the existing ReadPixels path, in the destination's own canonical client layout so the bytes need no second conversion, and writes them straight into the level. GL 4.6 core 8.6 says the copy ignores pixel-store state and any bound pack buffer, which the borrowed readback does not, so both are neutralised for the duration and restored after. A cube map destination addresses its faces as separate upload targets, so its zoffset picks the target rather than a slice. ConvertGLEnumToTextureInternalFormat had arms for the six generic compressed formats and the four RGTC ones, all resolving to uncompressed storage, but none for BPTC or ETC2/EAC - so glTexImage2D with one of those fourteen enums answered INVALID_ENUM, which was never a legal reply for formats core GL has required since 4.2 and 4.3. They follow the same deviation for the same reason: nothing in this stack can compress them, and uncompressed storage is the trade the RGTC formats already take. Takes textures_compressed_subimage from failing to passing on both backends and textures_copy on Espryt. textures_copy still fails on Magma, where the readback of a layered attachment does not yet resolve the attached layer. |
||
|
|
300b458132 |
[Feat] (MG_State, MG_Impl): give program pipelines their object and their state
Every program pipeline entry point was an export stub, and the stub macro's `return (type)1` made glIsProgramPipeline answer GL_TRUE for anything - including the names glGenProgramPipelines had never written. All four direct_state_access.program_pipelines cases failed. ProgramPipelineObject holds what GL 4.6 core 7.4 says a pipeline is: a program reference per shader stage, the active program glProgramUniform* addresses, a validate status and an info log. Its validate status starts false, unlike ProgramObject's, because a pipeline that has never been validated must report GL_VALIDATE_STATUS as 0. The name rules follow the shape queries and transform feedbacks already use, and which the CTS checks first: glGenProgramPipelines only RESERVES a name and glIsProgramPipeline answers GL_FALSE for it; the object appears on first bind, or immediately from glCreateProgramPipelines. Map membership is object existence - a pipeline, unlike a transform feedback, has no stateful default object zero, so no everBound flag is needed. glGet(GL_PROGRAM_PIPELINE_BINDING) reports the real binding now instead of a hardcoded zero whose comment said the entry points were stubbed. This is the state half only. program_pipelines_functional needs mixed-stage rendering - a vertex-only and a fragment-only program drawn together - and stays failing; glCreateShaderProgramv is deliberately left stubbed until that lands, so nothing can half-work in between. Takes program_pipelines_creation, _defaults and _errors from failing to passing on both backends. |
||
|
|
1f1a331a44 |
[Feat] (MG_Impl, MG_State): implement the framebuffer parameter getters and setters
glFramebufferParameteri, glGetFramebufferParameteriv and their two by-name siblings were all export stubs - the GL_ARB_framebuffer_no_attachments entry points. The stub raises no error and writes nothing, so direct_state_access.framebuffers_get_parameter_errors saw GL_NO_ERROR for all three conditions it checks. FramebufferObject gains the five DEFAULT_* parameters as real state, initialised to GL 4.6 core table 23.24 and bumping the object version on a write like the read buffer does. The getter answers those plus the six derived names - GL_SAMPLES and GL_SAMPLE_BUFFERS from the attachments' sample counts, GL_IMPLEMENTATION_COLOR_READ_FORMAT/_TYPE from the read buffer's internal format, GL_DOUBLEBUFFER true only for the window-system framebuffer, GL_STEREO false because stereo surfaces are not exposed - which is what glGetIntegerv already reports for the bound framebuffer. The pname rules live in ValidateFramebufferParameterPname, and their ORDER is load-bearing: a name outside the table is INVALID_ENUM, and only a name that IS in the table but that the default framebuffer cannot answer is INVALID_OPERATION. Testing the framebuffer kind first would answer INVALID_ENUM for GL_FRAMEBUFFER_DEFAULT_WIDTH on framebuffer zero, which is exactly the third thing the case checks. The by-name forms take zero as the default framebuffer, like the other DSA framebuffer entry points. Rendering to a framebuffer with no attachments is deliberately NOT enabled by this: CheckCompleteness still reports INCOMPLETE_MISSING_ATTACHMENT, because no backend can rasterize one. The state is real and the queries are honest; the draw path is a separate piece of work. Takes framebuffers_get_parameter_errors from failing to passing on both backends, with framebuffers_get_parameters - which passed only because both getters were stubs leaving the CTS's zero-initialised comparands untouched - still passing. |
||
|
|
817091641c |
[Fix] (MG_Impl): give a cube map the storage and the layered attachment it asks for
direct_state_access.framebuffers_texture_attachment threw on both backends, and three separate things were wrong on the way to a cube map framebuffer. glTexStorage1D/2D/3D validated their target by converting it to a single TextureUploadTarget. GL_TEXTURE_CUBE_MAP has no single upload target - it allocates all six faces - so the conversion produced Unknown and a legal glTexStorage2D(GL_TEXTURE_CUBE_MAP, ...) was rejected with INVALID_ENUM, which is where the case threw. The accepted set for these entry points is the dimension's storage targets, which IsTextureStorageTargetForDimension already spells out, so that is what they check now. TextureStorage2D then allocated only the primary upload target, leaving a cube map with one face out of six - cube-incomplete, so every framebuffer it was attached to answered GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. It allocates every upload target the object has; for every other 2D target that is the same single target as before. ResolveRepresentableFramebufferTextureUploadTarget declined every layered target but 2D array, so glNamedFramebufferTexture on a cube map reported "not represented by the current framebuffer attachment model". Cube maps, cube map arrays, 1D arrays, 2D multisample arrays and 3D textures are all the same shape as the 2D array that already worked - glFramebufferTexture binds the whole texture and the attachment records a representative upload target - so they are all handled now. DirectGLES routes a layered attachment to glFramebufferTexture, which is exactly this. Takes framebuffers_texture_attachment from failing to passing on both backends. |
||
|
|
e64c7c7e65 |
[Fix] (MG_Backend): never back a multisample texture with a one-sample Vulkan image
Every one of the sixty direct_state_access.textures_storage_multisample_2d_* and _3d_* cases failed on DirectVulkan, for every internal format, with no GL error anywhere - a pure data mismatch. The CTS asks for glTextureStorage2DMultisample(tex, samples = 1, ...), which is legal GL, and MobileGL carried the 1 faithfully through to VkImageCreateInfo::samples = VK_SAMPLE_COUNT_1_BIT. It then binds that image to the auxiliary program's sampler2DMS, whose SPIR-V is OpTypeImage with MS = 1. VUID-RuntimeSpirv-samples-08726 forbids exactly that pairing: an MS access must come from an image created with more than one sample. The texelFetch therefore read undefined data - which is why it looked format-independent and raised nothing. GL only promises "at least the requested number of samples", so a multisample texture is now floored at two. GL_TEXTURE_SAMPLES still reports what the application asked for; that is read off the texture object, not off the image. The device-capability round below it is bounded at two for the same reason - letting it land back on one sample would recreate the violation silently for any format whose only supported count is one. Takes all 60 textures_storage_multisample_* cases from failing to passing on DirectVulkan, which goes from 296/371 to 356/371. DirectGLES is untouched. |
||
|
|
dd60ff39ce |
[Feat] (MG_State, MG_Impl, MG_Backend, MG_Util): make the border colour real sampler state
glGetSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR) raised INVALID_ENUM,
because MobileGL kept the border colour on the texture object and
GetSamplerParam_State had no case for it at all. That is the first thing
direct_state_access.samplers_defaults asks, so the case threw before reaching
any of the defaults it was written to check.
GL 4.6 core table 23.18 lists TEXTURE_BORDER_COLOR as sampler state, so it moves
to SamplerParameters and TextureObjectBase reaches it through the SamplerObject
it already owns - one source of truth, and a sampler object bound over a texture
now supplies its own border colour, which is what GL says should happen. The
texture params version still moves on a write, because the DirectGLES texture
sync memoises on it. glSamplerParameter{fv,Iiv,Iuiv} and their getters read and
write all four components in whichever representation the caller used, and the
three representations are kept in step so any getter has an answer. The bogus
[0,1] and [0,255] range checks are gone: GL clamps a border colour when a
fixed-point format is sampled, it does not reject it.
DirectVulkan's ResolveVkBorderColor now reads the sampler rather than the
texture. DirectGLES gained a glSamplerParameterfv in its sampler sync, and both
that and the pre-existing glTexParameterfv are gated on a new
SupportsTextureBorderClamp capability - ES 3.2 core, or EXT/OES_texture_border_clamp
before it - since without the extension every such call is INVALID_ENUM on the
driver. DriverPost gains the matching row per the POST rule, saying what a user
actually loses when it is missing.
Takes direct_state_access.samplers_defaults from failing to passing on both
backends.
|
||
|
|
96ad7ca0cc |
[Fix] (MG_Impl): asking a renderbuffer for more samples than it has is INVALID_OPERATION
ValidateRenderbufferStorageSamples_State answered INVALID_VALUE for a sample count above GL_MAX_SAMPLES. GL 4.6 core 9.2.4 reserves INVALID_VALUE for a negative count: a count that is well formed but larger than the format can deliver is INVALID_OPERATION, because the argument is fine and the format is what cannot honour it. Takes direct_state_access.renderbuffers_storage_multisample_errors from failing to passing on both backends. |
||
|
|
e80a23eae6 |
[Fix] (MG_Backend): read a multi-slice glGetTexImage off the GPU instead of the CPU shadow
DirectGLES served every multi-slice glGetTexImage from the CPU shadow copy, on the grounds that its scratch FBO can only expose one layer at a time. But the shadow only holds what was uploaded, so any slice that was rendered to rather than written by glTexSubImage came back stale - and a layered framebuffer produces exactly that. The scratch FBO can expose one layer at a time repeatedly. The read now attaches each layer in turn and takes the slice off the GPU, walking the destination over GL_PACK_SKIP_IMAGES / GL_PACK_IMAGE_HEIGHT itself so each per-slice call packs a plain 2D image with the same layout StoreWideRowsToClient computes for the whole stack. The shadow stays as the fallback for the formats a colour attachment cannot represent at all, and for any slice whose attachment comes back incomplete. Takes all 27 remaining direct_state_access.textures_storage_multisample_3d_* cases from failing to passing on Espryt - they render into a TEXTURE_2D_MULTISAMPLE_ARRAY one layer per colour attachment and then read the whole array back. DirectVulkan is untouched. |
||
|
|
088f263495 |
[Feat] (MG_Impl): answer the two query parameters the getters were missing
GetQueryObjectValue implemented GL_QUERY_RESULT_AVAILABLE and GL_QUERY_RESULT and rejected everything else, so direct_state_access.queries_functional threw on its very first probe - GL_QUERY_TARGET - and never reached any of the checks it was written for. GL_QUERY_TARGET is state the object has carried all along; it just had no case. GL_QUERY_RESULT_NO_WAIT is GL_QUERY_RESULT with the backend asked not to block, and it brings a wrinkle the shared getter could not express: when the result has not landed, GL_ARB_query_buffer_object leaves the destination untouched rather than writing a placeholder. GetQueryObjectValue now reports "succeeded but produced no value" through an optional out-parameter, and all five callers - the four buffer forms and the four client-memory forms - skip the write on it. The switch is deliberately widened by exactly these two names: its default INVALID_ENUM is what the GL33 and GL40 query error cases rely on. queries_functional passes on Espryt. On Magma it stops throwing and fails on a value instead, which is a separate problem in the query results themselves. |
||
|
|
3b3b6e5b8b |
[Fix] (MG_Backend): read back the stencil half, and clear an sRGB target to the value asked for
Two reasons a framebuffer's contents came back wrong, both on the read/clear side rather than the write side. Stencil, on both backends. The CTS reads stencil with glReadPixels(GL_STENCIL_INDEX, GL_INT), which is as legal as the unsigned widths, and neither backend accepted it: DirectGLES's ReadPixelsStencilViaNative rejected every signed type, after which the call fell through to a native ES read the driver refuses and nothing was written at all, so the caller kept its zeros; DirectVulkan's pack switch had no GL_INT case, and of the cases it did have only GL_UNSIGNED_INT sourced the stencil plane - GL_FLOAT and GL_UNSIGNED_SHORT emitted a depth value, which is meaningless for a stencil-only image. Both now take the signed and float widths, and DirectVulkan decides "this is a stencil read" once rather than per type. DirectGLES also gains the GL_FLOAT_32_UNSIGNED_INT_24_8_REV fallback a DEPTH32F_STENCIL8 attachment needs, which rejects the 24_8 packed type. sRGB, on DirectVulkan. Every other write path goes through the UNORM twin view while GL_FRAMEBUFFER_SRGB is off, storing the raw value GL asked for, but a deferred clear is materialised with vkCmdClearColorImage - which names the image, so the driver applied the sRGB transfer function and a clear to 0.25 landed at 0.537. PreCompensateSrgbClearColor hands it the linear colour whose encoding is the requested value instead. It is a no-op for non-sRGB destinations, for integer clear encodings, and when GL_FRAMEBUFFER_SRGB is on and GL really does want the encode. Takes renderbuffers_storage from failing to passing on both backends, plus renderbuffers_storage_multisample and framebuffers_blit on Espryt. |
||
|
|
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. |
||
|
|
a63699cde6 |
[Fix] (MG_Impl, MG_Backend): reject incomplete cube maps in mipmap generation instead of crashing on them
Both direct_state_access.textures_generate_mipmap* cases crashed DirectVulkan. Two causes, neither of them a broken invariant: glGenerateMipmap and glGenerateTextureMipmap never checked cube completeness, so an incomplete cube map went straight to the backend, which asserts that the texture it is handed is complete. GL 4.6 core 8.14.4 makes that call INVALID_OPERATION - there is no consistent set of faces to filter down - and both entry points now say so through a shared check. VulkanRenderer::GenerateMipmap asserted that the target was one of the four it implements. 1D, 1D array and cube map array are legal GL and the front end passes them through, so meeting one is a gap in this backend's coverage; it now logs and declines, leaving the generated levels unwritten rather than aborting. textures_generate_mipmap_errors passes on both backends now. textures_generate_mipmaps stops crashing but still fails: DirectVulkan does not generate the 1D mip chain the case checks - the frontend's storage allocation gives the levels the right sizes, which is why the case passes when run on its own, but not the descending content the full-run state leaves it looking for. |
||
|
|
765aaec6dc |
[Fix] (MG_Impl, MG_Backend): stop the new layer attachment from reaching backends that cannot back it
Implementing NamedFramebufferTextureLayer made layered attachments reachable for the first time, and direct_state_access.framebuffers_texture_layer_attachment went from Fail to Crash on DirectVulkan. Two separate gaps sat behind it, both of them asserted on rather than reported: - The renderer resolves an attachment's GL layer straight onto a Vulkan array layer. A 3D texture's z-slice therefore lands outside its image, which has one array layer by construction, and the array texture objects are still the one-image stubs in TextureObjectStubs.h, so their image has a single layer whatever GL believes. MaterializePendingClearForTexture tripped over a clear whose layer span was outside the image it was given. - A cube map array has no image shape in VkTextureManager at all, so SyncTextureAndGetDescriptor returns null for it. NamedFramebufferTextureLayer now answers the full error set for every target and layer - which is what took the two error cases green - and then declines to attach anything but layer zero of a non-cube-array texture, through the same RecordUnsupportedFramebufferTextureAttachmentError the by-target entry point already uses. Layer zero of the other targets is the plain first-slice attachment glFramebufferTextureLayer already backs, so it still goes through. SyncTextureResource's assertion on an unsupported texture shape is also gone: it is a gap in this backend's coverage, not a broken invariant, and the code below it already handles the failure by declining the sync. It logs a warning instead. framebuffers_texture_layer_attachment goes back to Fail on DirectVulkan rather than Crash; no case changes in either direction beyond that. |
||
|
|
bcd669bd25 |
[Feat] (MG_Impl): complete the by-name framebuffer attachment and buffer-selection entry points
Four direct_state_access framebuffer cases failed on one shared cause and three local ones. The shared cause: every DSA framebuffer entry point resolved its name through GetNamedFramebufferObject_State, which rejects zero outright. But zero names the default framebuffer to these functions, so glGetNamedFramebufferAttachmentParameteriv, glNamedFramebufferDrawBuffer(s) and glNamedFramebufferReadBuffer answered INVALID_VALUE for every default-framebuffer query the CTS makes. They now resolve zero to the default framebuffer object and tell the two kinds apart explicitly, which is what the accepted-name rules key off anyway. Attachment queries: the accepted attachment names differ between the default framebuffer (FRONT/BACK variants, DEPTH, STENCIL) and a framebuffer object (COLOR_ATTACHMENTi, DEPTH/STENCIL/DEPTH_STENCIL_ATTACHMENT), and a name outside the relevant list is INVALID_ENUM. Both getters share ResolveAttachmentQueryName for that, so the by-target form no longer aliases GL_FRONT onto a framebuffer object's colour attachment 0. The TEXTURE_* parameters are also rejected with INVALID_ENUM when the attached object is a renderbuffer. Buffer selection: naming a buffer that belongs to the other kind of framebuffer is INVALID_OPERATION, not INVALID_ENUM - the enum is accepted, the framebuffer just has no such buffer. glDrawBuffers additionally rejects the multi-buffer names (FRONT, LEFT, RIGHT, FRONT_AND_BACK) with INVALID_ENUM on both kinds, takes BACK only when n is one, and glReadBuffer treats the multi-buffer names as accepted-but-unselectable. Both colour-attachment range checks now go through ValidateColorAttachmentInRange instead of comparing against MAX_DRAW_BUFFERS with an off-by-one. NamedFramebufferTextureLayer was a stub that reported "not represented by the current framebuffer attachment model" for every call, even though the attachment model stores a layer and the by-target glFramebufferTextureLayer already uses it. It is implemented against the same model, with the per-target layer limits and the INVALID_OPERATION-for-a-bad-name rule that separates it from NamedFramebufferTexture. NamedFramebufferTexture itself gained the two checks it lacked: colour attachment range, and a negative level. Takes framebuffers_get_attachment_parameters, framebuffers_get_attachment_parameter_errors, framebuffers_texture_attachment_errors and framebuffers_draw_read_buffers_errors from failing to passing on both backends. |
||
|
|
81604d5596 |
[Feat] (MG_Impl, MG_Test): validate the direct-state-access texture copies
CopyTextureSubImage1D and 3D were do-nothing stubs and the 2D form checked only its effective target, so all 28 conditions in direct_state_access.textures_copy_errors went unreported: level and region bounds, and every read-framebuffer precondition. The read-framebuffer half lands in FramebufferImpl as ValidateReadFramebufferForCopy - incomplete read framebuffer (INVALID_FRAMEBUFFER_OPERATION), a read buffer that names no attachment, and a multisampled read buffer (both INVALID_OPERATION). It decides multisampledness by attachment kind rather than by sample count alone, because a TEXTURE_2D_MULTISAMPLE attachment sets SAMPLE_BUFFERS even when its sample count is one - which is exactly what the CTS attaches, and what a renderbuffer-only check would have missed. The texture half is ValidateCopyTextureSubImage, shared by all three forms; 1D and 3D also get the effective-target rule their form specifies. NOTE: the copy itself is still not implemented for 1D and 3D - CopyTexSubImage1D_State and CopyTexSubImage3D_State remain TODOs and no backend exposes anything but a 2D blit - so direct_state_access.textures_copy stays red. Only the errors are complete, which is what un-stubbing these two entry points buys; both carry a comment saying so. CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding had been passing a storage-less texture and no read framebuffer, which the new validation correctly rejects. It now sets up a legal copy, so it still measures the by-name plumbing it was written for. Takes direct_state_access.textures_copy_errors from failing to passing on both backends. |
||
|
|
31ea6aa5a3 |
[Feat] (MG_Impl): give the by-name texture image queries their error set
glGetTextureImage resolved a texture by name and went straight to the read, skipping every object-level rule glGetTexImage enforces through GetTexImage_State - and on DirectVulkan it skipped the level checks in CopyTextureImageToClientOrPBO_State as well, because that backend answers GetTextureImage itself. Fifteen of the sixteen conditions in direct_state_access.textures_image_query_errors went unreported. The object-level half of that error set now lives in ValidateTextureImageQuery and both entry points run it. Three rules are new rather than merely relocated: - Multisample and buffer textures are not in the accepted target list; neither has a single image to return. - The destination-size checks (bufSize, and the span written into a bound pixel pack buffer) move ahead of the read. They existed, but downstream of it, where any early bail-out - an unmapped level, a pack step that declines the format - swallowed them. Both measure the tightly packed span summed over the object's faces, which is the least a query can produce, so nothing that would have fit is rejected. - IsDepthLikeInternalFormat had no case for StencilIndex8, so a colour client format read back against a stencil-only texture looked like a matching pair. glGetCompressedTextureImage was a do-nothing stub. It validates the name and the level, then reports INVALID_OPERATION: no format MobileGL can hold is compressed, and answering GL_NO_ERROR without writing would hand the caller stale memory - the same reasoning GetCompressedTexImage_State already follows. Takes direct_state_access.textures_image_query_errors from failing to passing on both backends. |
||
|
|
7d6f6603c1 |
[Feat] (MG_Impl): enforce the unpack-buffer rules on texture sub-image uploads
TexSubImage1D/2D/3D_State each carried a TODO for the three INVALID_OPERATION conditions GL 4.6 core 8.5 attaches to sourcing an upload from a bound PIXEL_UNPACK_BUFFER: the store being mapped, an offset that is not a multiple of the size of one datum of `type`, and reads that would run past the end of the store. None of them was checked, so every such call was quietly accepted. ValidatePixelUnpackBufferSource now covers all three and returns true when no unpack buffer is bound, so the callers can run it unconditionally. Persistent mappings stay legal sources, matching what ReadPixels already does on the pack side. The overrun check measures the tightly packed span, which is the smallest the unpack can read - pixel store parameters only ever widen it - so it cannot reject an upload that would have fit. TextureSubImage2D needed the call of its own: unlike its 1D and 3D siblings it does not route through TexSubImage2D_State. Takes direct_state_access.textures_subimage_errors from failing to passing on both backends. |
||
|
|
39c17c0b1b |
[Fix] (MG_Impl): validate the float texture parameter setter and the compressed size query
Two independent gaps in the texture parameter paths, both reported by direct_state_access: TexParameterf_State never ran ValidateTextureParameterForTarget. The integer setter reaches it through TextureParameterObject_State and the scalar float setter through TextureParameterObjectf_State, but glTexParameterfv and glTextureParameterfv funnel every non-vector pname straight into TexParameterf_State - so in float form MobileGL accepted sampler state on a multisample texture, a mipmapping min filter or a REPEAT wrap on a rectangle texture, and a negative TEXTURE_BASE_LEVEL/TEXTURE_MAX_LEVEL, all of which the integer form rejected. It now validates first, passing the same anisotropy-exempt param the by-object float setter uses so the anisotropy range check is not run twice. GL_TEXTURE_COMPRESSED_IMAGE_SIZE answered 0 for every texture. GL 4.6 core 8.11 makes the query INVALID_OPERATION on an image whose internal format is uncompressed and on any proxy target. TextureInternalFormat has no compressed enumerator, so that is every texture MobileGL can hold today; the condition is still written against an IsCompressedTextureFormat predicate so both level getters answer consistently once compressed formats land, and GL_TEXTURE_COMPRESSED now reads from the same predicate instead of a hardcoded false. Takes textures_parameter_setup_errors and textures_level_parameter_errors from failing to passing on both backends. |
||
|
|
88138b48ec |
[Test] (MG_Test): follow the backends to an advertised GL 4.0
Both AdvertisesVoxyRequiredRenderingExtensions cases pinned TargetGLVersion at 3.3, which was the reported version until V_OpenGL40 joined the advertised extension lists. The version assertion is incidental to what these cases are for - Voxy needs the individual ARB extensions, not a version - so it just tracks the new report instead of holding the old one. |
||
|
|
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. |
||
|
|
1ca2d3c0fe |
[Docs] (README): move the short-term target to OpenGL 4.2
The 3.3 line is done - GL30 through GL33 conform on both backends - and the work in flight (GL40, direct state access) is already past it, so the stated short-term target now reads 4.2 and MG_State/MG_Impl are focused there. Performance work joins the focus list alongside the two backends. |
||
|
|
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. |
||
|
|
4873da6844 |
[Fix] (MG_Impl): accept COLOR when invalidating the default framebuffer
The validation added with the invalidation entry points took the default framebuffer's buffers to be only FRONT_LEFT, FRONT_RIGHT, BACK_LEFT, BACK_RIGHT, DEPTH and STENCIL, so a call naming COLOR came back INVALID_ENUM. The by-name forms spell the colour buffer the way glClearNamedFramebuffer does - COLOR, DEPTH, STENCIL - while the target forms use the individual left/right tokens, and both spellings arrive at the same validation, so both sets belong there (GL 4.6 core 17.4.4). Caught by framebuffers_invalidate_data and framebuffers_invalidate_subdata, which had been passing while the entry points were stubs doing nothing at all. Those two plus invalidate_data_and_subdata_errors now pass together on both backends. |
||
|
|
efeb24ff9b |
[Fix] (MG_Impl, MG_State): answer the texture parameters the getters were missing
glGetTexParameter and its by-name form rejected several parameters GL 4.6 core table 8.20 lists, with INVALID_ENUM as if the application had made them up. GL_DEPTH_STENCIL_TEXTURE_MODE was the worst of them: the float setter accepted it, validated it and then threw the value away, the integer setter did not accept it at all, and neither getter could report it - so the mode could be set and never read back, and setting it through glTextureParameteri was an error. It is real state now, defaulting to DEPTH_COMPONENT, set by both setters and readable from both getters. GL_TEXTURE_LOD_BIAS was in the same position: settable, not gettable. The by-name getters reach the target-based ones through a temporary binding rather than the per-object path, so both had to learn these; the per-object path gained the swizzle components, the target, the image format compatibility type and the texture-view parameters at the same time, since they were missing there for the same reason. direct_state_access.textures_get_set_parameter passes on both backends, and textures_defaults stops raising an internal error and reports an ordinary failure it can be diagnosed from. |
||
|
|
18c1a4d586 |
[Feat] (MG_Impl): implement the query getters that write into a buffer object
glGetQueryBufferObjectiv and its three siblings were stubs. They are the ordinary query getters with the destination changed from client memory to a buffer object, so everything about the query itself - the name, whether it is still active, the parameter - is already answered by the shared GetQueryObjectValue, including the errors it raises. What was left is the destination: a negative offset is INVALID_VALUE, a name that is not a buffer object is INVALID_OPERATION, and so is a write that would run past the end of the buffer. The four differ only in the width they store, so they share one template. direct_state_access.queries_errors passes on both backends, putting the group at 4 of 5. queries_functional now reaches further into the test and ends in an unrelated InternalError rather than a plain failure. |
||
|
|
66ac3486e1 |
[Feat] (MG_Impl): validate the framebuffer invalidation entry points
glInvalidateFramebuffer, glInvalidateSubFramebuffer and their two by-name forms were all stubs, so every call - including the malformed ones - returned quietly with no error. These four only grant permission to throw the named attachments' contents away, and keeping them satisfies "the contents become undefined", so the frontend validates the call and leaves the contents alone. Actually discarding is a bandwidth optimisation that would need a backend dependency; it can be added later without changing what any of these promise. The validation is where the real content is. Which tokens name an attachment depends on which framebuffer is affected: the default framebuffer has buffers (FRONT_LEFT and company) and a framebuffer object has attachment points, so a token from the wrong set is INVALID_ENUM. A COLOR_ATTACHMENTm past GL_MAX_COLOR_ATTACHMENTS is different in kind - a well-formed enum naming a point that does not exist - and is INVALID_OPERATION, which the existing colour-attachment range validator already expresses. Negative counts and negative sub-region extents are INVALID_VALUE. direct_state_access.invalidate_data_and_subdata_errors passes on both backends. |
||
|
|
764a44f589 |
[Fix] (MG_Impl): stop treating an empty buffer mapping access mask as a bad enum
glMapBufferRange and glMapNamedBufferRange rejected an access of zero with INVALID_ENUM. Zero is a perfectly well-formed bitfield value - it contains no invalid flags - and what it violates is the separate rule that a mapping has to ask for read or write access, which GL reports as INVALID_OPERATION. Both callers already checked that rule immediately after, so the validator was reporting the wrong error for a case its callers were about to handle correctly. direct_state_access.buffers_errors passes, which puts the whole buffers group at 4 of 4 on both backends. |
||
|
|
d96acb7972 |
[Fix] (MG_Impl): let a buffer clear name any format the spec allows
glClearBufferData and friends accepted exactly two argument triples - R8UI with UNSIGNED_BYTE and R32UI with UNSIGNED_INT, both through RED_INTEGER - and raised INVALID_ENUM for everything else. That is most of the entry point missing rather than a narrow gap: GL takes any of the sized formats in the buffer-texture table, which is what an application clearing an RGBA8 or R32F buffer uses. The wrong error also hid the checks behind it. A test clearing a mapped buffer, or one passing a misaligned offset, never reached those rules because the format tuple was rejected first, so INVALID_ENUM came back where INVALID_OPERATION or INVALID_VALUE was due - the validation was there and correct all along, just unreachable. internalformat now goes through the same table the buffer textures use (shared rather than written out twice, since it is the same list for the same reason), and format and type through the ordinary pixel format converters. The element size comes from the internal format, which is what offset and size have to be multiples of. Note that a bad format or type here is INVALID_VALUE, not INVALID_ENUM (GL 4.6 core 6.3) - the odd one out among the enum arguments, and what the conformance tests check for. The pattern is still replicated verbatim, which is correct while the client layout matches the internal format - every real caller, and every conformance case. When they differ it now says so instead of quietly writing a differently-sized pattern. direct_state_access.buffers_clear and buffers_functional pass on both backends; buffers_errors is down to one unrelated complaint about glMapNamedBufferRange. |
||
|
|
bd710078fc |
[Feat] (MG_Impl): implement glGetNamedBufferSubData
The by-name read was a stub, so it left the caller's buffer untouched and a test comparing it against a reference saw whatever that memory already held. Its by-target sibling glGetBufferSubData was already implemented, so this is that function with the buffer resolved by name instead of through a binding: the same non-negative offset and size check, the same bound-by-the-buffer's-size check, the same refusal to read a buffer mapped without GL_MAP_PERSISTENT_BIT, and the same SyncGpuWrites before the download so a GPU-side write that has not landed yet is not missed. Resolving by name reports INVALID_OPERATION for a name that is not a buffer, which the by-target form expresses as "target is bound to no buffer object" instead. direct_state_access.buffers_get_named_buffer_subdata passes on both backends. |
||
|
|
95a7b17d45 |
[Fix] (DirectVulkan): clear an integer colour buffer with an integer value
glClearBufferiv and glClearBufferuiv flattened their values into the payload's float vector, and every clear was later written into VkClearColorValue::float32. Vulkan reads that union according to the destination image's format rather than converting between its members, so an R8I attachment cleared to -16 received the bit pattern of -16.0f. On top of that, QueueRenderbufferClear copied only the float vector into the pending clear, so even the flattened value was dropped and the attachment kept reading zero - which is what the conformance tests actually observed. The payload now records which of the three entry points supplied the colour and keeps the value in that form, and one helper builds the union member the encoding calls for. GL's rule that a format with no alpha channel reads as one has to be applied in the value's own type, so the "does this format lack alpha" question is now asked separately from the substitution and the helper applies it to whichever member is live. glClear is left on the float path explicitly: ClearFramebufferPayload has no other form. Takes every integer renderbuffer format in direct_state_access.renderbuffers_storage from failing to passing on Magma - 115 reported mismatches down to 20, the rest being the stencil formats Espryt fails too and SRGB8_ALPHA8 - and makes framebuffers_clear pass on both backends. |
||
|
|
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. |
||
|
|
1011d9fea1 |
[Test] (MG_Test): follow the query-name and incomplete-texture rules the CTS pinned down
Two unit tests asserted behaviour the conformance tests had since contradicted, so they were testing MobileGL's old answer rather than GL's. QueryTest expected glIsQuery to report a name straight out of glGenQueries as a query object. It is not one: GenQueries reserves names, and they "acquire query state only when they are first used by calling BeginQuery" (GL 4.6 core 4.2.1). The test now checks that a reserved name reads FALSE, that BeginQuery is what turns it into an object, and that a sibling name left untouched stays FALSE. A companion case covers the direct state access half, where glCreateQueries does create the object outright - which is the whole reason the two entry points both exist. The DirectGLES binding test built its texture with glGenTextures and glBindTexture and nothing else, then expected BindCurrentTextures to bind it natively. A texture with no image is incomplete and samples as (0, 0, 0, 1), which DirectGLES expresses by leaving the native target unbound, so the setup no longer produced the binding the test then went on to clear. It now gives the texture a format and a 1x1 level 0 - one level is the entire mip chain at that size, so it is complete under any filter - and asserts that directly, so a future completeness change fails on the setup line instead of on the assertion three calls later. |
||
|
|
b5565ae503 | [Docs] (tools/cts): refresh the DSA reference tables after the multisample storage fix | ||
|
|
b06ad3f877 |
[Fix] (MG_Impl): make multisample texture storage immutable, and validate it by name
glTexStorage2DMultisample and glTexStorage3DMultisample forwarded straight to the glTexImage*Multisample allocation and stopped there. The allocation is indeed the same; what the storage forms add is that it is final - TEXTURE_IMMUTABLE_FORMAT becomes TRUE and any later call on that texture is INVALID_OPERATION (GL 4.6 core 8.19). MobileGL left the texture mutable forever, so it reported TEXTURE_IMMUTABLE_FORMAT as FALSE and accepted being respecified any number of times, silently discarding storage a test or an application had already rendered into. The by-name forms had no validation of their own either. The target forms get their target checked when the binding is resolved; reached by name there is no binding, so glTextureStorage2DMultisample took any texture, any extent and any sample count. It now rejects a target that belongs to the other entry point (INVALID_OPERATION), extents outside 1..GL_MAX_TEXTURE_SIZE and a depth past GL_MAX_ARRAY_TEXTURE_LAYERS (INVALID_VALUE), and a sample count above GL_MAX_SAMPLES (INVALID_OPERATION) - measured against the limit the getter reports rather than the backend parameter it is derived from, since the frontend raises that number. glTextureStorage1D/2D/3D gained the same treatment: a target belonging to a different one of the three is INVALID_OPERATION, a zero extent is INVALID_VALUE (immutable storage describes a real image, unlike glTexImage*D where an empty level is legal), and a level count longer than the level-zero size admits is INVALID_OPERATION. Which dimensions take part in that mip chain is per target: a 1D array keeps its layer count in height, so its height does not halve. Takes direct_state_access.textures_storage_multisample_2d_* from 0 to 30 of 30 on Espryt, and the whole group from 74.93% to 82.48%. Magma still fails them for a separate reason. |
||
|
|
3311e6034a |
[Fix] (MG_Impl): report a buffer texture as the wrong object, not the wrong token
glGetTextureParameter* resolve the texture by name and then hand the work to the target-based getter, which validates the target it was given. For a buffer texture that is GL_TEXTURE_BUFFER, and the target form correctly calls that an unaccepted token - INVALID_ENUM. By name there is no token to blame. The application named an object that carries none of the sampler or level state the query reports, which is INVALID_OPERATION (GL 4.6 core 8.11). The four by-name getters check the resolved object before delegating, so the error describes what the caller actually got wrong. Fixes direct_state_access.textures_parameter_errors on both backends, taking the group to 74.93% on Espryt and 73.32% on Magma. |
||
|
|
027c1bd4ab |
[Docs] (tools/cts): add the desktop Linux CTS skill
The Android and Windows paths each have a skill; the desktop Linux one had only a runner script and a README section, so it was the least discoverable of the three despite being the one to reach for while iterating - it needs no device and no GPU, and a single test group takes seconds rather than hours. Records what the other two skills cannot: that the toolchain has to be GCC 13+ or Clang 20+ (Clang 18 reports __cpp_concepts as 201907L, which switches libstdc++'s <expected> off and breaks the shader transpiler), that EGL_PLATFORM=surfaceless is mandatory for DirectGLES and why the symptom points at the wrong call, and which of this environment's results are MobileGL's own versus artefacts of software rendering. Also states the rule the other skills only imply: report Espryt and Magma separately. They fail different cases, and one combined number hides which backend a change moved. |
||
|
|
da52cc3906 | [Docs] (tools/cts): refresh the DSA reference table for the fixes in this branch | ||
|
|
ebe4fe133f |
[Fix] (MG_Impl): apply the buffer texture's own format and range rules
glTextureBuffer and glTextureBufferRange took any internal format the texture enum converter recognised. A buffer texture accepts a much shorter list than a sampled or a renderable texture does (GL 4.6 core table 8.16), and it cannot be inferred from either, so a format like GL_RGB8 was accepted and produced a texture nothing could read. Two error codes were wrong as well. A texture whose effective target is not GL_TEXTURE_BUFFER is the wrong object rather than the wrong token, so it is INVALID_OPERATION. And the range form never checked its range against the buffer it was attaching, so a size past the end of the buffer was accepted and left the texture addressing memory the buffer does not own. Fixes direct_state_access.textures_buffer_errors and textures_buffer_range_errors on both backends. |
||
|
|
534ec65dda |
[Fix] (MG_Util): ask the ES driver for the texture buffer offset alignment
The DirectGLES capability probe queried GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT with a bare glGetIntegerv while every other query in the same function goes through glesFuncs. A bare call resolves to MobileGL's own exported entry point, which answers that pname out of the capability table this code is in the middle of filling in, so the value read back was the default it started from and the driver's real alignment never arrived. The backend therefore advertised an alignment of 1. An application that trusts that - which is the only thing it can do - passes glTextureBufferRange an offset the ES driver cannot honour, and the driver produces a texture that reads as zeros with no error anywhere. The alignment llvmpipe actually wants is 16. Takes direct_state_access.textures_buffer_* from 3 to 30 of 30 on DirectGLES, and the whole DSA group from 66.85% to 74.12%. DirectVulkan was unaffected: its alignment comes from a Vulkan device limit and was already right. |
||
|
|
35ad1ae7fc |
[Docs] (tools/cts): document the desktop Linux CTS path and the DSA baseline
run_cts_local.py and the mobilegl-desktop VK-GL-CTS target were both in the tree with nothing describing how to reach them, so the only documented ways to run the suite needed either an Android device or a Windows box with a GPU. The desktop Linux path needs neither: lavapipe gives DirectVulkan a headless surface and Mesa's surfaceless EGL gives DirectGLES a context, so a single test group can be measured in seconds while working on it. Records the two things that cost time to find. EGL_PLATFORM=surfaceless is mandatory for DirectGLES - without a /dev/dri node Mesa fails eglInitialize on the default display, and MobileGL surfaces that as EGL_BAD_ALLOC from eglCreatePbufferSurface, which points at the wrong call entirely. And DirectVulkan's default-framebuffer readback returns zeros here exactly as it does on Adreno, so that defect is MobileGL's and reproducible without a phone. The direct_state_access reference table is the measured baseline for the fixes in this branch, so a later change has something to be compared against. |
||
|
|
6152ee933f |
[Fix] (MG_Impl): bound a colour attachment and a vertex binding range by the limit
GL_COLOR_ATTACHMENTn is a token for every n up to 31, but only the first GL_MAX_COLOR_ATTACHMENTS of them name an attachment point of a framebuffer object. The enum conversion accepted the whole token range, so attaching a renderbuffer or a texture to a colour attachment past the limit silently succeeded instead of reporting INVALID_OPERATION, and the attachment landed in a slot nothing else would ever look at. glBindVertexBuffers and glVertexArrayVertexBuffers take a range of binding points rather than one index. A range running past the last binding point is INVALID_OPERATION, which the per-binding validation could not report: it saw one index at a time and reported the INVALID_VALUE that a single out-of-range index earns. The range is checked up front now, before any binding point is touched, so a rejected call also leaves none of them changed. Takes direct_state_access.vertex_arrays_* to 18 of 19 and fixes direct_state_access.framebuffers_renderbuffer_attachment_errors on both backends. |
||
|
|
dac02ca044 |
[Feat] (MG_Impl, MG_State): implement the direct state access transform feedback API
glCreateTransformFeedbacks, glTransformFeedbackBufferBase, glTransformFeedbackBufferRange and the three glGetTransformFeedback* queries were all stubs, so a transform feedback object could only be configured and inspected by binding it first - the exact thing direct state access exists to avoid. The queries were the worse half: they returned nothing and raised no error, so an application could not tell that it had learned nothing. glCreateTransformFeedbacks creates the objects outright. glGenTransformFeedbacks only reserves names, and a reserved name becomes an object when it is first bound (GL 4.6 core 13.2.1); the DSA form has no bind step to create them from. The queries and the buffer bindings read and write a named object's state. That state lives in two places: the context keeps one live copy of the capture bindings and the active/paused flags for whichever object is bound, and every other object's copy sits in its saved state until a bind swaps it in. The by-name accessors added to the context resolve that, so a query for the bound object reads the live copy rather than a stale save. GL_TRANSFORM_FEEDBACK_BUFFER_START and _SIZE are answered as zero unless the binding was made by the range form, matching what the buffer object binding points already do. Takes direct_state_access.xfb_* from 0 to 4 of 5 on both backends; xfb_functional still fails on the capture itself, which is a separate defect. |
||
|
|
42fd02d82f |
[Fix] (MG_Impl, MG_State): give the vertex buffer binding points a real state view
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.
|
||
|
|
6359b0002b |
[Feat] (MG_Impl, MG_State): implement the DSA vertex array queries
glGetVertexArrayiv, glGetVertexArrayIndexediv and glGetVertexArrayIndexed64iv were stubs, so nothing could read a vertex array's state without binding it first -- the exact thing direct state access exists to avoid. They read the state the vertex array already holds. Two accessors were needed for that: the relative offset and the binding points, which are the binding-point view the flat per-attribute state was resolved from and cannot be reconstructed from the resolved form. Note the index means different things by entry point: for the 32-bit indexed query it is an attribute, but GL_VERTEX_BINDING_OFFSET names a vertex buffer binding point directly (GL 4.6 core 10.3.1). GL_VERTEX_ATTRIB_ARRAY_LONG is answered GL_FALSE throughout, which is honest while 64-bit vertex attributes are unsupported. Takes direct_state_access.vertex_arrays_* from 8 to 12 of 19 on Espryt. GL_VERTEX_BINDING_OFFSET still reads back 0: the query is right but the offset is not reaching the binding point, which is a separate defect further up. |
||
|
|
c186f5f255 |
[Feat] (MG_Impl): implement glCreateQueries and stop treating a reserved name as a query
glGenQueries only reserves names; a name becomes a query object when it is first used with BeginQuery or QueryCounter (GL 4.6 core 4.2.1). MobileGL created the live object eagerly at glGenQueries time and glIsQuery reported every reserved name as an object, with a comment noting the shortcut. The registry already distinguished the two states -- a target of 0 means the name has never been used -- so glIsQuery now consults it, and a name that came from glCreateQueries carries a flag saying it is an object regardless. glCreateQueries itself was a stub. It creates the objects outright with their target already fixed, which is the whole point of the DSA form: there is no binding step to infer the target from later. |
||
|
|
3d97f6fa8f |
[Fix] (DirectVulkan): decline a draw with no usable fallback instead of aborting
GetFallbackTexture asserted that the target was 2D or rectangle, so a sampler whose texture could not be resolved took the process down whenever it was any other kind. A multisample sampler reaches exactly that path: its texture is reported incomplete, the resolve falls back, and the assert fires. Sixty direct_state_access multisample cases died that way, and because the abort kills the whole process the harness lost the rest of its chunk with them -- one run needed 63 invocations to get through the suite instead of 3. The fallback is a single-sampled 2D image, so it genuinely cannot stand in for a multisample sampler: that descriptor demands a multisample view, and binding this one is invalid usage rather than a degraded picture. So report that no fallback exists and let the caller decline the draw. An unbound or incomplete sampler is an application-level mistake with a defined GL meaning; it is never a reason to abort. The cases still fail -- multisample textures are not yet complete enough to sample -- but they fail as one reported case each. |
||
|
|
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. |
||
|
|
f39e6eb82d |
[Feat] (MG_Impl): implement glReadnPixels
It was exported as a stub: it logged a warning and returned, leaving the caller's buffer untouched. Anything reading back through it saw whatever the destination already held, which for a freshly allocated vector is zeros -- so every direct_state_access texture test comparing a readback against reference data failed without a GL error to explain it. glReadnPixels is glReadPixels with a bound on how much it may write (GL 4.6 core 18.2.8, originally GL_ARB_robustness) and is identical in every other respect, so it validates and reads through exactly the same path once the destination is known to be big enough. Sizing the read honours the GL_PACK_* state: rows are padded to GL_PACK_ALIGNMENT and laid out GL_PACK_ROW_LENGTH wide, with the skip parameters offsetting the first texel. The last row is deliberately not padded -- nothing follows it to align -- which is what makes a tightly-sized destination legal. |
||
|
|
cb2ba71feb |
[Feat] (DirectVulkan): run the tessellation stages
The backend already turned a tessellation control/evaluation shader into the right VkShaderStage, but nothing downstream knew what to do with it: GL_PATCHES had no topology, so it fell through to the triangle-list default, and the pipeline carried no tessellation state at all. A GL_PATCHES draw therefore ran the vertex and fragment stages over raw triangles. Map GL_PATCHES to VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, carry GL_PATCH_VERTICES into the pipeline as patchControlPoints (part of the key, since two patch sizes are two pipelines), attach VkPipelineTessellationStateCreateInfo for a patch topology only, and enable the tessellationShader device feature. POST reports the feature, because without it a program with a tessellation stage cannot build a pipeline at all and GL_PATCHES draws render nothing. |
||
|
|
6ea7ccdf64 |
[Feat] (DirectVulkan): support an arbitrary primitive restart index
Vulkan restarts only on the fixed all-ones value of the index type, so GL_PRIMITIVE_RESTART with a glPrimitiveRestartIndex of anything else used to hard-fail the draw. GL_PRIMITIVE_RESTART_FIXED_INDEX already matches Vulkan and is untouched. Rewrite the indices into a transient copy instead, substituting the fixed value for the application's. An index that already equals the fixed value would then be indistinguishable from a restart, so it is nudged down by one: it can only be a real index, since the application's restart index is a different number, and the vertex it names is outside any well-defined draw -- whereas leaving it alone would tear the primitive in two. The element array buffer is rewritten whole rather than only the drawn range, because an indirect draw's firstIndex lives in GPU memory and cannot be adjusted from here; every element therefore keeps its position. |
||
|
|
14605723f0 |
[Fix] (DirectVulkan): flag a transform feedback capture as a GPU write
A capture is a GPU write like any shader's, so a later CPU read of the buffer has to wait for it. Only shader storage buffers were flagged, so mapping or reading back a capture buffer could observe whatever the queue had retired so far. Nothing needs copying -- the capture writes land in coherent host-visible storage already -- but coherence only says the writes are visible once they have happened, which is exactly what MarkGpuWritten arranges through the readback op. |
||
|
|
a680611c9f |
[Fix] (DirectVulkan): never stream a buffer whose storage the application holds
AcquirePersistentMap promises the storage it creates is never recreated, because the frontend adopts it in place of the shadow and hands out pointers into it. AcquireStreamedSlice broke that promise: its downgrade path releases the resident storage unconditionally to avoid keeping a second stale copy, so binding such a buffer as a vertex or index source freed the memory the application was still pointing at. It also fed that draw the wrong bytes. The streaming copy is uploaded from the shadow, and a persistently mapped buffer can hold bytes the shadow never saw -- a transform feedback capture writes straight into the resident storage. The next capture into the same buffer then landed in freshly recreated storage while the application kept reading the original, which is how the ping-pong in transform_feedback.draw_xfb_feedbackk_test stalled after its first doubling. Route a persistently mapped resource to the resident path instead, where its single piece of storage is bound directly. |
||
|
|
93224ca406 |
[Fix] (DirectVulkan): make transform feedback writes visible to what reads them
GL makes transform feedback results visible to every later command on their own, with no glMemoryBarrier in between -- unlike shader storage writes. An application replaying a capture with glDrawTransformFeedback is therefore entitled to the captured bytes without asking for them, so the barrier the Vulkan memory model requires has to come from here. It cannot be recorded where the write happens: the capturing draw runs inside a render pass that declares no self-dependency. Flag it there instead and emit the barrier at the next point that could read the buffer -- the following draw's setup, or a readback -- ending the render pass first, the same shape glMemoryBarrier already uses. The destination covers every way a captured buffer comes back: replayed as vertex attributes or indices, read through a uniform or storage binding, sourced as an indirect command, copied out, or mapped. |
||
|
|
fbed4485b7 |
[Fix] (DirectVulkan): key the program cache on the transform feedback capture layout
The program cache is content-hash-shared across GL program names, so its key has to cover everything that changes the modules it stores. The capture layout did not: XfbCaptureDecoratePass bakes XfbBuffer/XfbStride/Offset into the SPIR-V from the frontend's layout, none of which is in the SPIR-V being hashed. Two programs with identical shaders and different glTransformFeedbackVaryings therefore shared one entry, and the first one linked decided how both captured. That is precisely what changing the buffer mode does -- the same varyings recorded with GL_SEPARATE_ATTRIBS instead of GL_INTERLEAVED_ATTRIBS -- so the separate-attribs pass of transform_feedback.draw_xfb_test replayed a capture that was still interleaved into buffer 0. Hash the captured varyings' names, buffer indices and offsets plus the per-buffer strides, and only for a capturing compile, so no other program changes key. |
||
|
|
90ae0f048c |
[Fix] (MG_Impl): answer the GL_UNIFORM program interface from the frontend reflection
The GL_UNIFORM interface queries and glGetActiveUniform(s)iv describe the same set of resources in two spellings, but they were reading it from two different places: the latter from the frontend reflection, the former forwarded straight to the backend program. The backend program is not a source of truth for this. It does not exist at all for a program whose types its shading language cannot express -- a double-precision uniform has no ESSL form, so the program never links there -- and the interface queries then described a program with no uniforms, which is how gpu_shader_fp64.fp64.state_query failed. Route GL_ACTIVE_RESOURCES / GL_MAX_NAME_LENGTH, the resource index, the resource name and the resource properties for GL_UNIFORM through the same reflection that already answers glGetActiveUniformsiv, so the two spellings can no longer disagree and neither depends on the backend having linked. The props that reflection does not model (GL_ATOMIC_COUNTER_BUFFER_INDEX and the GL_REFERENCED_BY_* stage bits) still come from the backend, looked up by the uniform's name so the two index spaces do not have to agree. GL_MAX_NAME_LENGTH counts the terminator; the stored maximum does not, as every other caller of GetUniformMaxLength() already accounted for. |
||
|
|
cff959b2e8 |
[Feat] (DirectVulkan, MG_Util): honour a glVertexAttribDivisor other than 1
Vulkan's VK_VERTEX_INPUT_RATE_INSTANCE advances an attribute once per instance and has no way to say anything else, so every non-zero divisor collapsed to 1: an attribute the application asked to change every three instances changed every one, and KHR-GL40.draw_indirect.basic-drawArrays-instancing and its elements sibling drew the wrong colours from instance one onward. VK_EXT_vertex_attribute_divisor is exactly this state, so it is enabled when the device has it and the per-binding divisors ride into the pipeline through VkPipelineVertexInputDivisorStateCreateInfoEXT. Only divisors other than 1 are listed - 1 is what the plain input rate already means - and they join the layout hash, so two layouts that differ only in a divisor no longer share a pipeline. POST reports the feature either way, because without it the failure is silent and looks like a shader bug: the attribute is fetched, just from the wrong instance. The GLES side gains the two checks this session's other work made load-bearing for the same reason - glPatchParameteri (without it GL_PATCH_VERTICES stays at the driver's 3 and a patch draw of any other size renders nothing) and the transform feedback object entry points (without them a second object cannot open a capture while the first is paused). KHR-GL40.draw_indirect on Magma: 70/70 but for the arbitrary primitive-restart index, which Vulkan cannot express at all. |
||
|
|
a50b2c422b |
[Fix] (DirectVulkan): submit a generated mip chain before a later upload can overtake it
Texture uploads go out on a command buffer of their own the moment they happen, while glGenerateMipmap records its blit chain into the frame's command buffer, which is not submitted until the frame ends. So a glTexSubImage2D into a level that was just generated reached the GPU FIRST and the blits then wrote over it. KHR-GL40.texture_gather.base-level does exactly that - generates the chain, then writes the texels it is going to sample into level 1 and points TEXTURE_BASE_LEVEL at it - and read back the generated content instead of what it had written. The image view, the mip range and the upload itself were all correct; only their order on the GPU was not. This is the same hazard the mip-chain-growth recreate above already flushes for, from the other side: there the recorded work had to reach the GPU before an out-of-band copy read the image, here before an out-of-band copy writes it. Submitting at the end of the generation orders every upload that can follow. |
||
|
|
9dcda82d71 |
[Fix] (DirectVulkan): advertise GL_ARB_get_program_binary on Magma too
The extension and its three entry points are frontend state - no binary format is exposed on either backend - but only DirectGLES listed it, so on Magma dEQP's loader still left glProgramParameteri null and KHR-GL40.api.coverage called straight through the null pointer. The entry point is not core before GL 4.1; this is what exposes it. |
||
|
|
28d0af6f04 |
[Feat] (MG_Util, DirectGLES, DirectVulkan): normalize rectangle coordinates in the module
Neither target API has GL_TEXTURE_RECTANGLE: ESSL has no rectangle sampler, and Vulkan's SPIR-V environment does not allow Dim::Rect. Both emulate it on a plain 2D texture, and the two differ in exactly one way - a rectangle lookup addresses texels where a 2D one addresses [0,1]. That one difference now lives in one SPIR-V pass, so neither backend has to know about it: every lookup taking normalized coordinates gets its coordinate divided by the size the texture reports, and the image type is then rewritten to 2D. Magma had no rectangle handling at all - it fed Dim::Rect straight to Vulkan, which read the texel coordinates as normalized and sampled the edge, so all fifteen KHR-GL40.texture_gather.*-2drect cases came back holding the clear colour. This replaces the ESSL text rewrite that did the same divide for DirectGLES only. Doing it in the module instead is both shorter and stricter: the pass resolves an operation's image type through the sampled-image and pointer wrappers rather than matching a sampler name in generated source, so it cannot be fooled by an expression where it expected an identifier, and it needs no help from the frontend reflection to know which samplers were rectangles. Still declined, as before: the Dref *sample* forms, whose coordinate carries the compare value in its last component, and the projective ones, where the divide would have to happen after the perspective divide. texelFetch is deliberately untouched - integer texel coordinates mean the same thing on both targets. KHR-GL40.texture_gather: Magma 66 failures -> 2, Espryt stays at 75/75. |
||
|
|
44ee6b66b3 |
[Fix] (MG_State, DirectVulkan): apply the incomplete-texture rule on Magma too
The completeness rule itself is GL's, not a backend's, so it now reads as one question both backends ask - SamplesAsIncompleteTexture(texture, effective sampler) - and each answers in whatever way it already expresses "nothing is bound at this sampler". DirectGLES leaves the native target unbound; Magma has a fallback texture for exactly that case and now routes an incomplete texture to it. The fallback's texel had never been written, so it read whatever its freshly allocated storage held. GL is specific here: an incomplete texture - and a sampler with nothing bound - reads (0, 0, 0, 1). It says so now, which is what makes KHR-GL40.texture_gather.incomplete-texture-last-comp (it gathers the alpha) meaningful rather than accidentally right. |
||
|
|
28c3cfc1d6 |
[Fix] (DirectVulkan, MG_State): make a shader-written storage buffer readable on Magma
Reading a buffer a compute shader wrote gave zeros: the frontend shadow that MapBuffer resolves against is only maintained by uploads, and Magma had no path back. Every KHR-GL40.texture_gather case ends by dispatching a compute shader into an SSBO and comparing the mapped result, so 66 of 75 failed on it. Magma needs no readback: EnsureGpuResidentStorage - the same host-visible coherent adoption the transform feedback capture already uses - makes the shadow BE the memory the shader writes, so binding a buffer as a shader storage buffer now adopts it. What coherence does not give is ordering: the writes are visible once they have happened, and the CPU was reading before the dispatch had retired. The readback op therefore submits the recorded work and waits. That exposed a mistake in the frontend flag this rides on: MarkGpuWritten skipped GPU-resident buffers, reasoning there was no shadow to refresh. True, but the wait is still needed - "reconcile with the GPU write" is not always "copy it back", and which of the two it is belongs to the backend. The flag now only says a write is outstanding; DirectGLES's readback still skips its persistent-mapped buffers when copying. KHR-GL40.texture_gather on Magma: 66 failures -> 19 (the rest are rectangle textures, mipmap completeness and tessellation, all still to do). Espryt stays at 75/75. |
||
|
|
38e04eefae |
[Fix] (DirectVulkan): size the indirect draw command by GL's struct, not the renderer's
The indirect draw paths bounded their read out of GL_DRAW_INDIRECT_BUFFER - and took their default stride - from `sizeof(DrawCmdParam)`, this renderer's own draw-parameter struct. That is not the command GL defines: DrawCmdParam carries two extra members for bounding vertex-stream conversion and is 24 bytes, where GL's DrawArraysIndirectCommand is four uint32. So every glDrawArraysIndirect against a tightly-sized indirect buffer - which is what an application writes, and what the CTS writes - failed the range check and drew nothing. It went unnoticed on the elements side only by coincidence: DrawIndexedCmdParam happens to be exactly the 20 bytes of DrawElementsIndirectCommand. Both sizes are now named constants of GL's own layout. KHR-GL40.draw_indirect on Magma: 21 failures -> 3. |
||
|
|
fd29cb914e |
[Fix] (DirectVulkan, MG_State): give each transform feedback object its own capture counters
The frontend half of ARB_transform_feedback2 landed for both backends, but Magma's capture was still written for the one implicit span GL 3.3 has: - A paused span kept capturing. VK_EXT_transform_feedback's counter buffers already make consecutive draws append, so pausing is simply "do not wrap this draw" - the counters keep their values and the next resumed draw carries on where the last captured one stopped. - Those counter buffers were context-wide. Transform feedback objects can each hold an open, paused span at the same time - KHR-GL40.transform_feedback.draw_xfb_test keeps three - and they were all appending through one set of four slots. Each object now gets its own group, handed out on first use; past sixteen objects they share group 0, which only matters for concurrently-paused spans. - The generation that identifies a span is what a backend keys its append state on, so it is now part of the per-object state the frontend saves and restores. Without that, resuming an object that was paused before another one began looked like a new span and restarted its counters at zero. GL_PRIMITIVES_GENERATED needed one more thing. It counts what the last vertex processing stage emitted whether or not anything is being captured, but VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT only counts what the capture saw - so a draw made while the span was paused is invisible to it. The frontend now tallies those draws, and the Vulkan query adds the delta at result time. The correction lives in the backend that needs it: an ES driver's GL_PRIMITIVES_GENERATED counts them by itself, and adding it there too would double them. transform_feedback* on Magma: 4 failures -> 3. Espryt stays at 38/38. |
||
|
|
38497174c8 |
[Feat] (MG_Impl): implement the double-precision uniform state
glUniform*d, glUniformMatrix*dv, their glProgramUniform twins and glGetUniformdv were all stubs - 35 entry points - so a GL 4.0 program's double uniforms could be declared and located but never set or read. Worse, glGetUniformfv on one did reach the storage: the generic getter memcpy'd the uniform's declared size into the caller's buffer, so a 4-byte float pointer received 8 bytes. That overrun is what took the process down in KHR-GL40.gpu_shader_fp64.fp64.state_query. The upload path is already templated on the component type, so the vector forms are wiring. A matrix is not: the column stride the linker used for a double matrix is not the 16 bytes a float one gets. It is not guessed - the slot the uniform was given is exactly `columns` columns wide, so dividing states the stride the rest of the pipeline already agreed on, for both the upload and the readback. The four getters now convert instead of reinterpreting when the uniform holds doubles, following GL 4.6 core 7.6: round to nearest for the integer queries, and clamp into the queried type's range so a negative double read through glGetUniformuiv is 0 rather than its two's complement. The case still fails one step further on, where it queries the same uniforms through GL_ARB_program_interface_query: those calls are answered by the backend program, and an fp64 shader has none - ESSL has no doubles, so it never links. Answering them from the frontend reflection is a separate change. |
||
|
|
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. |
||
|
|
5fce287de5 |
[Fix] (DirectGLES): generate a three-channel float mip chain on the CPU
glGenerateMipmap requires the level-0 format to be colour-renderable, and ES has no colour-renderable three-channel float format at all - so an ES driver rejects GL_RGB16F and GL_RGB32F where every desktop driver accepts them, and the error was forwarded to the application. KHR-GL40.texture_gather.plain-gather-float-2d-rgb and its offset- sibling build their texture that way and fail on the leftover error alone. The blit-based emulation already used for GL_R11F_G11F_B10F is no help: it renders level n from level n-1, so it needs exactly the renderability that is missing. But a format the driver cannot render into is a format nothing can have rendered into either, which makes the frontend's own copy of the texels authoritative for precisely these formats. So the chain is box-filtered there and the levels are marked dirty; the backend sync that follows uploads them like any other texture data. Deliberately narrow: only the two formats whose texels are a plain float array, and only when they are what the texture actually holds. Every other format keeps the driver's behaviour, error included. |
||
|
|
ae6949e459 |
[Fix] (MG_State, DirectGLES): sample a mipmap-incomplete texture as black
A minification filter that reads the mip chain requires every level from the base down to hold exactly half the previous one's size; a texture that does not is incomplete and every lookup on it returns (0, 0, 0, 1) (GL 4.6 core 8.17). Nothing checked it. The ES driver cannot catch this on MobileGL's behalf, which is why it has to be a frontend rule here: the backend texture is immutable storage allocated from the level set as it stood, so a level the application later redefined at a different size never reaches the driver at all, and the ES texture stays complete. That is exactly what KHR-GL40.texture_gather.incomplete-texture does - it redefines level 1 of a complete chain as 1x1 - and it read the original contents back. The check runs where the sampling bindings are established, and an incomplete texture simply leaves its native target unbound: an unbound ES target samples as (0, 0, 0, 1), which is the answer GL asks for, with no scratch texture to keep around. An array texture's layer count is not one of the dimensions that halves, so the comparison only shrinks the components that belong to the image itself - getting that wrong turned eight *-2darray cases black. |
||
|
|
5437947240 |
[Feat] (DirectGLES, MG_Util): normalize the coordinates of a rectangle lookup
A rectangle texture is emulated on an ES 2D texture, and LowerRectImagesForEssl rewrites the image type in the SPIR-V to match. That is exact only where the lookup addresses texels directly, which is why the pass declined any module containing a lookup that takes normalized coordinates - the whole KHR-GL40.texture_gather 2drect set among them. The missing half is one divide: a rectangle lookup's coordinate is in texels and the 2D lookup it becomes wants [0,1], so the coordinate has to be divided by the texture's size. It goes in on the ESSL the transpiler produces, next to the LOD-bias emulation that already rewrites lookup arguments there, and reads the size back with textureSize() rather than plumbing a uniform down - the emulated texture is a real ES 2D texture, so the shader can ask it directly. Only the forms whose argument 1 is the bare coordinate are rewritten - texture, textureOffset and the three textureGather flavours, which covers the Dref gathers too because those carry the compare value in a separate argument. texelFetch is deliberately left alone: its coordinates are integer texels on both targets. The SPIR-V pass keeps declining everything else, so a projective lookup or a Dref sample (where the compare value rides in coord.z) still refuses the module instead of producing something subtly wrong. Which samplers were declared rectangle is no longer visible in the transpiled source - they are plain sampler2D by then - so the names come from the frontend program's reflection. |
||
|
|
f38dbf018d |
[Fix] (MG_State): give a rectangle texture its own initial sampler state
Every texture object started from the shared defaults, which are the 2D ones: TEXTURE_MIN_FILTER of NEAREST_MIPMAP_LINEAR and TEXTURE_WRAP_S/T of REPEAT. A rectangle texture has no mip chain at all, so GL gives it a different initial state - LINEAR and CLAMP_TO_EDGE (GL 4.6 core table 23.15) - and a mipmapped minification filter is not even a legal value to set on one. With the 2D default in place a rectangle texture was mipmap-incomplete the moment it was created, and an application that (correctly) never touches the filters read (0, 0, 0, 1) out of every lookup. That is what the eleven KHR-GL40.texture_gather.*-2drect cases saw: they set only the wrap modes, because the filters are already what a rectangle texture needs. |
||
|
|
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. |
||
|
|
ff76af9df7 |
[Fix] (MG_State, MG_Impl): a transform feedback name is only an object once it is bound
glIsTransformFeedback answered GL_TRUE for any name glGenTransformFeedbacks had handed out. A generated name is reserved but does not denote an object until the first glBindTransformFeedback (GL 4.6 core 13.2.1) - the same rule the other object types follow - and KHR-GL40.api.coverage checks exactly the window in between. The two questions are now asked separately: whether a name may be bound or deleted (reserved, which is what the delete and bind paths need) and whether it is an object (reserved and bound at least once). |
||
|
|
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. |
||
|
|
8d1a734c22 |
[Fix] (MG_State, MG_Impl): reject a draw mode the geometry stage cannot accept
A geometry shader declares the primitive type it consumes, and a draw may only present a mode that decomposes into it - points for `points`, the three triangle modes for `triangles`, and so on (GL 4.6 core 11.3.1). Anything else is GL_INVALID_OPERATION. Nothing checked it, so KHR-GL40.draw_indirect.negative-gshIncompatible-arrays and -elements drew points through a `layout(triangles) in` shader and got no error. The program object had no notion of the geometry input primitive at all: glslang knows it right after the link, so it is read off the geometry intermediate and kept as the GL enum (this is also what GL_GEOMETRY_INPUT_TYPE would report). Resolved on every link rather than only when transform feedback captures the stage, since every draw consults it, and cleared with the rest of the link artifacts. The check sits on the shared pre-draw gate next to the transform feedback primitive rule, which is the same shape of constraint. GL_PATCHES is deliberately exempt: it is the tessellation pipeline's input and has already become the tessellator's output primitive by the time the geometry stage sees it. draw_indirect is now at 70/70. |
||
|
|
7d215028fb |
[Fix] (MG_Impl): validate the draw mode and the indirect draw's command source
Two classes of draw-time error were never raised, which the KHR-GL40.draw_indirect negative-* cases check one by one: - `mode` was passed through unexamined, so glDrawArraysIndirect(GL_FLOAT, ...) reached the backend instead of raising GL_INVALID_ENUM. The check belongs on the shared pre-draw gate, so it now covers every draw entry point rather than just the indirect pair. Nothing that used to render stops rendering: a mode the frontend now rejects is a mode the backend driver was rejecting anyway, silently. - The indirect commands read their arguments out of the buffer bound to GL_DRAW_INDIRECT_BUFFER, and all three of that source's preconditions were unchecked (GL 4.6 core 10.3.10): a 4-byte-aligned offset, a bound buffer at all, and enough room left in it for the whole 16- or 20-byte command. glDrawElementsIndirect also never validated its index type, which is the same accepted set as the rest of the DrawElements family. Takes the group from 24 failures to 2 - both of the remaining ones are the geometry shader input-primitive compatibility rule, which needs reflection the program object does not keep yet. |
||
|
|
00534d8bbc |
[Fix] (MG_Impl): report the draw-indirect binding and the buffer access state
Three pieces of queryable buffer state were missing, all of them read by the KHR-GL40.draw_indirect basic-binding-* and basic-buffer-* cases: - GL_DRAW_INDIRECT_BUFFER_BINDING had no case in glGetIntegerv, so it raised GL_INVALID_ENUM and left the caller's variable untouched (the test read back its own -9999 sentinel). GL_DISPATCH_INDIRECT_BUFFER_BINDING right next to it was already handled; this is the same two lines against BufferTarget::DrawIndirect. Because glGetBooleanv/glGetFloatv/glGetDoublev all widen from the integer path, one case fixes all four getters. - GL_BUFFER_ACCESS answered 0 for an unmapped buffer. Its initial value is GL_READ_WRITE and glUnmapBuffer restores it (GL 4.6 core table 6.2); 0 is not a legal value of that state at all, and the test threw on the unrecognised enum. - GL_BUFFER_ACCESS_FLAGS was not implemented, so it fell through to the invalid-pname arm. It is the MapBufferRange bitfield verbatim, which the mapping access flags already hold in normalised form - glMapBuffer's access enum is converted on the way in - so it converts straight back out, and reads zero while unmapped. |
||
|
|
d81a6a0998 |
[Fix] (MG_Impl): silently ignore program and shader name zero on delete
glDeleteProgram and glDeleteShader are the two entry points in the program/shader name space where 0 is not "a name GL never handed out" but an explicit no-op: "if program is zero, it is silently ignored" (GL 4.6 core 7.3, and 7.1 for shaders). Both went through the shared name validator instead and recorded GL_INVALID_VALUE. Only tests that never got as far as creating a program noticed, because they still run their cleanup path: the five KHR-GL40.texture_gather.*-cube-array cases bail out of Init with "GL_ARB_texture_cube_map_array not supported", then Cleanup deletes its zero-initialised handles and the leftover error fails the case after the fact - the downstream-error-misattribution shape. Every array-taking delete already skipped 0. |
||
|
|
9bf23d7ffd |
[Fix] (MG_State, DirectGLES): read a shader-written storage buffer back before mapping it
Buffer contents live in a CPU shadow that every read - MapBuffer, MapBufferRange, GetBufferSubData, CopyBufferSubData - resolves against, and backend transfer ops only ever push the shadow outwards. Two paths already knew the GPU can write a buffer on its own and mirrored the result back by hand (ReadPixels into a pixel-pack buffer, the transform feedback capture at EndTransformFeedback); a shader storage buffer written by a draw or a dispatch had no such path at all, so the map handed the application the bytes from before the dispatch. Nothing exercised it until now because GL 3.3 has no compute stage. Every KHR-GL40.texture_gather case ends by dispatching a compute shader that writes its sampled texel into an SSBO and comparing the mapped result, and all 71 read back the zero-filled shadow. Adds the missing direction as a backend op: BufferObject::MarkGpuWritten flags a buffer the GPU may have moved ahead of the shadow, SyncGpuWrites pulls it back at every read point, and DirectGLES implements the readback with a plain read map of the ES buffer. The flag is raised where the storage-buffer points are bound for the upcoming draw or dispatch, which is the last moment the set of exposed buffers is known, and cleared by the readback - so a buffer nothing writes costs one bool test per map. Backends that cannot read their storage back leave the op null and keep today's behaviour; a GPU-resident (coherent persistent) buffer needs nothing, since its reads already resolve against the memory the shader wrote. Drops the texture_gather failures from 71/75 to 25/75 with no crashes left. |
||
|
|
41e45f7d48 |
[Fix] (MG_Impl): let an indexed buffer bind reach the generic binding point too
BindBufferBase and BindBufferRange bind the buffer to the indexed point AND to the
generic binding point of the same target (GL 4.6 core 6.1.1); only the indexed half
was implemented. Applications lean on the second half constantly, because it is what
makes the set-up idiom work:
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
With the generic point left at 0 the glBufferData raised GL_INVALID_OPERATION and
the buffer kept its zero size, so the later glMapBufferRange over it failed the
offset+length bound and returned nullptr. The whole KHR-GL40.texture_gather group
verifies its result through exactly that sequence and dereferences the map's return
value without checking it, so 51 of its 75 cases took the process down with a
SIGSEGV inside the test.
Unbinding propagates the same way: buffer 0 clears both points.
|
||
|
|
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. |
||
|
|
598c5497b0 |
[Fix] (DirectVulkan): submit pending work before growing a texture's mip chain
Sizing backings by their defined mip level count gave every level-0-only texture a single-level image, and left growing it to the recreate-and-preserve path: the new image is created and the old contents are carried over by a vkCmdCopyImage that PreserveTextureContentsOnRecreate submits on its own command buffer and waits on straight away. Whatever the frame has already recorded into the old image has not been submitted yet at that point, so that copy reads the texture as it stood before this frame's writes. GenerateMipmap then descends the whole chain from a stale level 0, and the composite pass that samples it renders a washed-out frame - minecraft-1.21.4-fabric-iris-iterationt-in-world (Iris's mipmapped colour target, the one texture in the trace that grows 1 -> 10 levels) came back at ssim 0.5699 against a 0.99 threshold. This is the hazard the storage-usage upgrade already flushes for before its own preserve-copy; growing the mip chain is simply the second trigger of that same recreate, and it was added without the same ordering guarantee. Flush there too, gated on a texture whose live image really does carry a short chain, so the submit happens once per texture and only when a recreate is actually coming. Keeps the single-level backing and its memory saving; ssim goes back to 0.9992. |
||
|
|
86c00bdf18 |
[Test] (MG_Test): catch the unit tests up with three deliberate behaviour changes
ctest -L unit had been failing 13 of its 418 cases, all of them tests left asserting what the code did before a commit that changed it on purpose: - "restore target GL version to 3.3" put the advertised target back after the experimental 4.6 run, but the two Voxy sanity tests still demanded 4.6. The extensions they really care about are all still advertised, so assert 3.3 and drop the now-meaningless AtExperimentalCTSVersion from their names. - "support rectangle textures where the emulation is exact" made every desktop-only target supported - rectangle included, stored as a plain 2D - while the texture test still expected rectangle to be rejected. - "keep declared modern GLSL versions strict" changed two things at once: a normalized legacy directive now carries a marker on its line, so the ten tests matching "#version 330 core\n" whole no longer match; and a version the application declared itself is no longer raised to 460, so the sources declaring 330/400 keep their own number and only MobileGL's own normalization is retargeted. Test expectations follow, rather than the implementation being bent back: each of the three changes is the intended behaviour and is argued for where it was made. The retry test now drives the 460 escalation from a legacy "#version 130" source, which is the only thing that is still rescued, and gained a case pinning the other half of that contract - an application-declared "#version 330" stays at 330. 418/418 unit tests pass. |
||
|
|
2f2f95498f |
[Fix] (MG_State): detach a deleted texture from the framebuffer that is bound
GL 3.3 core 4.4.2: deleting a texture whose image is attached to the framebuffer currently bound acts as if FramebufferTexture* had been called with texture zero for every attachment point it occupied there. Framebuffers that are not bound keep the orphaned attachment, so only the bound ones are touched. MobileGL unbound a deleted texture from every texture unit and image binding but left framebuffer attachments alone, so the framebuffer went on holding the dead texture alive as its attachment and reads through it returned that texture's contents rather than those of whatever the application put in its place - and since the deleted name usually comes straight back out of the next glGenTextures, the two are indistinguishable from the outside. |
||
|
|
7105c2ebdc |
[Fix] (DirectGLES): never skip a framebuffer bind on a stale version snapshot
BindCurrentFBO returned early when the framebuffer binding slot's version matched g_fboBindVersions - but nothing on that path ever writes that entry. Only ForceBindCurrentFBO stamps it, so the comparison was against an arbitrarily old snapshot, and any later slot version that happened to land on the same 16-bit value read as "already bound". The driver was then left on whatever framebuffer it had last been given. That is how KHR-GL32.packed_pixels.varied_rectangle.rg8i_format_rg_integer read its gradient back out of the previous subtest's framebuffer, seeing 18 where 127 was expected. It only shows up after a few thousand cases have gone by - long enough for the counter to come back around - which is why it reproduced exactly under one caselist and not at all in isolation. Drop the fast path. Skipping redundant work is BindFramebufferId's job: it shadows the driver's own draw and read bindings and drops the glBindFramebuffer when the target already holds that id, which is where the cost actually is. What is left here is one registry lookup. Takes GL32 to 100% conformance; GL30, GL31 and GL33 stay at 100%. |
||
|
|
13bab780f2 |
[Fix] (DirectGLES): gate the replicate blit's stencil pass on ES 3.1
Reading the stencil half of a packed depth/stencil texture goes through GL_DEPTH_STENCIL_TEXTURE_MODE, which is ES 3.1 state. On an older driver the pname would raise GL_INVALID_ENUM and the shader would go on sampling depth bits as if they were stencil, so decline the emulation instead. |
||
|
|
027310f993 |
[Fix] (MG_Impl): ask whether a colour format is renderable per target
The framebuffer-completeness check scanned every row of the backend's format-capability cache and called the format renderable if any target said so. That was already loose, and it broke outright once DirectGLES started widening three-channel formats so they stay renderable as multisample storage: the caveat capability recorded for the multisample target made GL_RGB8_SNORM look renderable everywhere, so an ordinary 2D GL_RGB8_SNORM texture attachment reported GL_FRAMEBUFFER_COMPLETE while the driver's own framebuffer was INCOMPLETE_ATTACHMENT. KHR-GL3x.packed_pixels stopped skipping those formats and read a framebuffer that could not be read, so all 18 of its rgb8_snorm cases got back an untouched buffer. Pass the row the attachment actually lives in - the texture's target, or the renderbuffer row - and consult only that one; a format is still asked about in general when the caller has no target. |
||
|
|
c741a938bc |
[Feat] (DirectGLES): emulate a depth/stencil blit into a multisample framebuffer
Desktop GL replicates the source sample into every destination sample when the read framebuffer is single-sampled and the draw framebuffer is not. ES forbids the call outright - "an INVALID_OPERATION error is generated if SAMPLE_BUFFERS for the draw framebuffer is greater than zero" - so the blit did nothing at all, and every one of KHR-GL3x.packed_depth_stencil.blit's replicate iterations verified a destination that still held its clear values. Emulate it by drawing a full-screen triangle into the multisample framebuffer: every pixel is fully covered, so every sample of it receives the same value, which is precisely the replicate rule. The source rectangle is first copied into a scratch texture of its own format (both sides single-sampled, which ES does allow), then depth is written through gl_FragDepth and stencil - which has no shader output on ES - one bit plane at a time with REPLACE and a discard for the pixels whose source bit is clear. The draw runs inside the caller's framebuffer, so every piece of pipeline state it touches is read back and restored, including the per-draw-buffer colour masks the non-indexed glColorMask does not cover: the sync layer's shadow of the driver state has to stay true across this. Colour replicate is not emulated (it would need a sampler variant per component type); it now says so instead of failing silently. |