Vulkan has no LINE_LOOP topology and the frontend used to reject the mode
with GL_INVALID_OPERATION, which is itself non-conformant (several KHR-GL33
transform_feedback tests draw line loops and expect no error). DrawArrays,
DrawElements and DrawElementsBaseVertex now rewrite the draw into an
indexed GL_LINE_STRIP whose synthesized uint32 index list revisits the
first vertex, delivered through the client-memory index path (a new
forceClientMemory flag keeps a bound element-array buffer from hijacking
the synthesized pointer). Entry points without the rewrite degrade to an
open line strip instead of a triangle list.
BlitFramebuffer's color path asserted that the read framebuffer's source
attachment is a texture; a renderbuffer source (packed_depth_stencil.blit
color checks) aborted the process. Materialize pending clears through the
renderbuffer path for both source and destination, as ReadPixels already
does.
glRenderbufferStorageMultisample accepts any sample count up to MAX_SAMPLES
(including non-powers-of-two like 3) and promises at-least allocation, but
the renderbuffer path required an exact Vulkan sample-count match and failed
on devices like llvmpipe that expose 1x/4x only. Round the request up to a
power of two and then to the nearest count the device supports for the
format, cached per format so per-draw resolution does not re-query the
physical device.
Un-crashes KHR-GL33.packed_depth_stencil.blit.* (2x/3x MSAA renderbuffers).
UploadDirtyMipLevels used to skip D24S8/D32FS8 textures outright, leaving
glTexImage-supplied depth-stencil data unuploaded (KHR-GL33
texture_repeat_mode depth24_stencil8 and texture_swizzle depth-stencil
cases all sampled zeros). De-interleave the shadow's GL wire format into a
depth plane (X8_D24 word / float) and a stencil byte plane and record one
copy per aspect, with cross-conversion when the device backs the texture
with the other depth-stencil format.
Depth32FStencil8's shadow byte size also claimed 16 bytes/texel while the
stored wire format (GL_FLOAT_32_UNSIGNED_INT_24_8_REV) is 8; that mismatch
truncated every upload of it.
Also route a multisample-texture sample-count request through the device's
supported counts (round up, GL promises at-least semantics).
UploadDirtyMipLevels encoded a texture's GL depth into VkBufferImageCopy
imageExtent.depth with layerCount = 1. For array textures the layers live in
the image's arrayLayers, and extent.depth > 1 is invalid for 2D images - in
practice every layer past the first never received its data.
Route the third dimension into layerCount for 1D/2D/cube array images and
keep imageExtent.depth for genuine 3D images.
Fixes the KHR-GL33.pixelstoragemodes.teximage3d.* failures (110 cases) on
lavapipe.
The legacy-shader retry that retargets a failed parse to #version 460 also
re-legalized multidimensional arrays, which every desktop driver rejects
below 430 and KHR-GL33.shaders.arrays.invalid.* requires to fail. Skip the
retry when the original failure is glslang's arrays-of-arrays error; other
legacy rescues (e.g. layout(binding=...)) keep working.
KHR-GL33.shaders.arrays.invalid.multidimensional_array* now report the
required compile failure (4 cases).
A cached RenderPassEntry bakes renderbuffer clear payloads inline into its
pendingClearAttachments, and that list outlives the clear's consumption at
pass begin (loadOp CLEAR). Every subsequent draw that reused the entry while
its pass was still active replayed the stale clear through
vkCmdClearAttachments, wiping the color and depth of everything drawn so far
in the pass.
Texture-keyed clears already re-checked the clear manager before clearing;
do the same for inline renderbuffer payloads: only clear while the
renderbuffer clear is still actually pending, and take the live payload so a
newer glClear's values win.
On lavapipe this takes KHR-GL33.shaders.fragdepth.* from 0/18 to 18/18; the
same defect hit any renderbuffer-FBO case with several draws per pass.
With no GL_ELEMENT_ARRAY_BUFFER bound, the IndexBufferView byte offset is a
raw client pointer (desktop drivers accept client-memory indices and the GL
CTS relies on this even in core contexts). UploadAndBindIndexBuffer used to
assert-crash the process there; it now snapshots the client index data into
a transient per-frame slice and binds that, matching how client-memory
vertex attributes are already streamed.
Fixes the process abort in KHR-GL33.transform_feedback.capture_* and every
other mustpass case that draws with client-side index arrays.
Local counterpart of run_cts.py for desktop Linux runs: re-invokes glcts
with the not-yet-measured cases after a crash, quarantines timed-out cases
with the dEQP watchdog enabled, and records crashed/hung/unrun lists so a
partial run cannot read as a complete one.
Guard the AImageReader window path behind __ANDROID__ and add a
mobilegl-desktop DEQP target so glcts can run against libMobileGL.so on a
Linux host via pbuffer surfaces (VK_EXT_headless_surface).
- every render pass declared colorAttachmentCount=8 (the full GL draw-buffer
slot span) with trailing VK_ATTACHMENT_UNUSED references, and Adreno
configures its per-pixel render-backend/export path from the DECLARED
count - so every fragment of every pass paid an 8-render-target export
cost; this was the bulk of the 1.5x per-pixel gap against
MobileGlues+ANGLE on the same Qualcomm driver (their subpasses declare
exactly the used span)
- measured on Adreno 650 / MC 26.2 / 1440x3044: total GPU frame time
11.9 -> 7.5 ms (-37%, now below ANGLE's 7.87 ms), the single-quad
swapchain blit pass alone 1.26 -> 0.40 ms, steady in-world FPS 82.8 -> 123
under the standard cooled-start protocol, matching the
MobileGlues+ANGLE+system-Vulkan benchmark of 123.8
- trailing UNUSED references are popped before the subpass is built (the
entry's colorAttachmentCount and every pipeline's colour-blend span follow
it); interior GL_NONE holes keep their slots so fragment-output locations
still line up
- the pipeline-side fragmentOutputMask check downgrades from assert to a
debug log: an output at a location past the trimmed span is discarded,
which is GL's defined behaviour for a draw buffer set to GL_NONE
- viewport, scissor, blend constants, depth bias, line width and the six
stencil parameters were re-emitted unconditionally for EVERY draw (~1500
vkCmdSet* per frame in MC 26.2, where ANGLE emits a handful), costing CPU
record time and GPU command-processor work for values that almost never
change between draws
- a recording-scoped shadow now drops any vkCmdSet* whose values match what
the command buffer already holds; valid because every PipelineFactory
pipeline declares the same eight dynamic states, so set values persist
across those binds
- the shadow resets at every command-buffer (re)begin (dynamic state does
not survive the boundary) and after binding the blit or depth-mipmap
pipelines, whose narrower dynamic sets make the untouched states undefined
and whose raw viewport/scissor writes bypass the shadow
- every non-MSAA texture was allocated with a full mip chain regardless of
how many levels the GL texture actually defines, so MC's 3044x1440 main
colour and depth render targets each carried 12 levels where ANGLE
allocates one; a level-0-only texture now gets a single-level backing and
upgrades to the full chain exactly once when a second level is first
defined, through the existing preserve-copy recreation path
- saves a third of the memory of every mip-less texture and keeps
single-level render targets off the multi-mip image layout entirely, which
also removes the surface the Adreno 650 implicit-LOD overread workaround
(ForceExplicitLod0SamplePass) exists to defend
- measured perf-neutral on Adreno 650 / MC 26.2 (the driver keeps full UBWC
on multi-mip render targets), so this is a memory/robustness fix, not a
speed one
- a draw whose sampled texture needs out-of-pass work (deferred clear
materialization or a sampled-layout transition) used to end the active
render pass - a full-target store+reload on a tiler - even when the only
ordering the work needs is 'before this draw'; MC 26.2 clears an overlay
texture every frame and samples it mid-pass, splitting the main scene pass
once per frame for nothing
- every frame slot now carries a second primary command buffer, submitted
strictly AHEAD of the frame command buffer in the same vkQueueSubmit; when
the open recording has not referenced the image yet (tracked via a
recording-generation stamp on the texture resource, advanced on every
frame-command-buffer begin and stamped at every recorded reference:
attachments at BeginRenderPass/attachment-write, sampled reads per draw,
layout transitions), the clear/transition is recorded there and the active
pass stays open - ANGLE's outside-render-pass command stream, restricted
to the provably reorderable case
- mid-frame flushes and readback submits close and carry the pre stream with
the frame buffer (it must never be submitted later than the recording it
was paired with), retiring both under the same submit index; dropped
recordings (present suspension, swapchain recreation) abandon it
- MaterializePendingClearForTexture's no-active-render-pass assert now
applies only to the frame command buffer, since the pre stream records
while a pass is open on the frame buffer by design
- EGL swap semantics make the presented colour buffer's content undefined at
its next acquire (EGL_BUFFER_DESTROYED, the implementation default) and
every ancillary depth/stencil buffer's content undefined after ANY swap,
yet the default-FBO render pass reloaded both with LOAD_OP_LOAD every
frame; SwapchainObject now tracks per-image content validity (defined when
a pass stores into the attachment, invalidated at present) and the
render-pass manager turns an undefined attachment's tile load into
LOAD_OP_DONT_CARE with initialLayout=UNDEFINED, keyed into both hashes so
the cached LOAD variants cannot be hit by mistake
- the default framebuffer's depth attachment is now attached ON DEMAND: a
draw with depth test and stencil test both disabled (GL: a disabled test
neither reads nor writes its buffer), and no pending depth/stencil clear,
resolves to a depth-less pass flavour, dropping the D24S8 tile load AND
store outright - MC 26.2 renders its GUI into its own FBO and only ever
blits colour to the default framebuffer, so its swapchain pass carried a
full-screen depth round-trip for nothing
- the flavour only escalates: an active depth-full pass absorbs depth-less
draws unchanged, while a depth-using draw against a depth-less pass
resolves to an incompatible entry and splits, its depth loading DONT_CARE
(the content was undefined all along); the depth-less flavour is folded
into ComputeHash and the per-draw fast-path memo so the two flavours can
never alias
- Every storage-capable colour texture was created MUTABLE_FORMAT, and Adreno
gives up bandwidth compression on an image that may be viewed as any format in
its compatibility class. MC's main render target therefore ran uncompressed;
in a fill-bound scene that is the whole frame budget. Measured on Adreno 650,
MC 26.2, same scene and camera, device cooled to 38-40C before each run:
65.3 -> 80.9 fps (+23.9%), GPU busy ~93% in both.
- VK_KHR_image_format_list (enabled when present) fixes it without giving up
mutability: VkImageFormatListCreateInfo names the exact formats a view may
use, so the driver can keep the image compressed. The set must be exhaustive
or the result is undefined - for sampled views it is exactly what
ResolveSampledImageViewFormat can return over the three numeric domains.
- glBindImageTexture may name any compatible format, which cannot be enumerated
ahead of time, so a texture bound to an image unit gets no format list. That
is what VK_IMAGE_USAGE_STORAGE_BIT becoming on-demand is for: it makes
"unmarked" mean "will never receive an arbitrary-format storage view", which
is what makes the list sound. Removing STORAGE is worth nothing on its own
(65.4 fps, measured) - only the mutability bound pays.
- MarkStorageImageTexture runs over every collected image-unit texture before
the probe loop in PrepareStorageImageTextures, because that loop stops at the
first texture needing work and would leave the rest unmarked. The mark makes
NeedsStorageImagePreparation report true, which is what ends the render pass,
so the recreate lands outside it.
- storageUsageResolved separates "not upgraded yet" from "this format can never
carry STORAGE", so a format whose optimalTilingFeatures lack STORAGE_IMAGE
cannot ask for a recreate that will never happen. SyncTexture's cross-draw
early-out also has to break on a pending upgrade or the recreate never runs.
- An upgrade recreates the image and carries its contents forward through
PreserveTextureContentsOnRecreate, which submits its own command buffer and
waits. Whatever the frame already recorded into the old image is still
unsubmitted, so that copy would read pre-frame content and this frame's
rendering into the texture would be lost - exactly the render-target-then-
image-unit case. PrepareStorageImageTextures now flushes first; it takes the
FrameData rather than a command buffer because the flush retires the current
one, and drops the sampled-descriptor-set memo that described it.