Commit Graph
479 Commits
Author SHA1 Message Date
BZLZHH 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.
2026-08-04 11:55:17 -04:00
BZLZHH 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.
2026-08-04 11:47:35 -04:00
BZLZHH 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.
2026-08-04 11:42:44 -04:00
BZLZHH 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.
2026-08-02 13:09:48 -04:00
BZLZHH 4a533a215a [Chore] (MG_Backend): fix compiling error. 2026-08-01 23:21:35 +08:00
BZLZHH 9192d156d1 [Fix] (DirectVulkan): ReadPixels materializes pending clears before resolving the blit binding
ResolveColorBlitBinding cached a RenderbufferResource*/TextureResource*
(trackedLayout) before the pending-clear materialization step ran. For an
attachment that had never been part of any render pass yet (e.g. a
GL_NONE draw buffer slot read back via an explicit glReadBuffer), the
materialize call was the first thing to touch its resource, and creating
that entry in the UnorderedMap (FastSTL, open-addressing) can rehash and
invalidate every previously-taken pointer into the map - including the
one just cached. The read then saw a stale VK_IMAGE_LAYOUT_UNDEFINED and
silently bailed (via a compiled-out MGLOG_E in release builds), leaving
the client buffer untouched. Reordering so the clear is materialized
first, then the binding resolved, guarantees the pointer reflects the
final resource state. Fixes KHR-GL3{0,1,2,3}.draw_buffers.draw_buffers_1.
2026-08-01 08:09:49 -04:00
BZLZHH d18c6a1bae [Fix] (DirectVulkan): Xlib surface fallback for ICDs without VK_EXT_headless_surface
Real drivers (NVIDIA proprietary Linux) don't implement VK_EXT_headless_surface,
which the pbuffer path required unconditionally, hard-aborting at CreateInstance.
CreateInstance now detects instance-extension support and requests
VK_KHR_xlib_surface instead when headless is unavailable; CreateSurface creates
an unmapped Xlib window purely to obtain a VkSurfaceKHR, then proceeds through
the existing swapchain path unchanged. Shutdown destroys the window it owns.
Lavapipe and other headless-capable ICDs are unaffected.
2026-08-01 06:38:30 -04:00
BZLZHH 0c1a433af6 [Fix] (DirectVulkan): multisample resolve blits; per-buffer blit skip; RGBA-widened renderbuffers
Color blits from a multisampled source now use vkCmdResolveImage (both
blit resolvers carry the image sample count); a buffer named in the blit
mask but absent from either framebuffer skips just that buffer instead
of cancelling the whole blit (GL 4.6 18.3.1); and three-channel color
renderbuffers widen to their RGBA twin exactly like textures, so
renderbuffer<->texture blits of the same GL format see one VkFormat.
framebuffer_blit.multisampled_to_singlesampled_blit_color_config_test
passes - the whole framebuffer_blit family is green.
2026-08-01 02:46:16 -04:00
BZLZHH a2f3efe22c [Feat] (DirectVulkan): combined and scissored blits; cross-format depth-stencil blits
BlitFramebuffer now serves any GL_COLOR/DEPTH/STENCIL mask combination:
the depth/stencil aspects run as per-aspect image copies before the color
path, renderbuffer attachments materialize their pending clears like
texture ones, and the scissor test clips blit writes (destination rect
intersected, source shrunk proportionally). Depth copies between images
of different depth formats (a D24S8 renderbuffer into a
DEPTH_COMPONENT24 texture riding the D32_SFLOAT fallback) round-trip
through the host with a per-texel re-encode; stencil aspects pass
through raw since every packed format encodes S8. scissor_blit and
packed_depth_stencil.blit.* now pass.
2026-08-01 02:19:07 -04:00
BZLZHH b9a15aed61 [Feat] (DirectVulkan, MG_Impl): GPU transform feedback primitive queries
The TF primitive queries now ride VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT
pools when the device reports transformFeedbackQueries: each captured draw
is wrapped in a slot (shared between both GL targets when active
together), and results sum the (written, needed) pairs -
GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN from the first,
GL_PRIMITIVES_GENERATED from the second. This is exact through geometry
shaders, so KHR-GL33.transform_feedback.query_geometry_* pass; the CPU
accounting delta remains the fallback for backends without the feature.
2026-08-01 02:02:35 -04:00
BZLZHH 107b56d603 [Feat] (DirectVulkan, MG_Impl): occlusion queries via Vulkan query pools
GL_SAMPLES_PASSED / GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) now work: every
app draw between Begin/EndQuery is wrapped in a slot of a host-reset
occlusion query pool (precise counts when occlusionQueryPrecise is
granted), and the result flush ends any active render pass before
submitting, waits, sums the slots and recycles them. ANY_* targets
report the boolean form; GL_QUERY_COUNTER_BITS and GL_CURRENT_QUERY
answer for the occlusion targets, and deleting an active query releases
its slot. Draw-time depth/stencil state also honors attachment absence:
a framebuffer without a depth (stencil) attachment behaves as if that
test always passes, even when a packed depth-stencil image is attached
through only one half (verify_partial_attachments.*).
2026-08-01 01:50:34 -04:00
BZLZHH 22b749dd37 [Fix] (MG_Util, DirectVulkan): canonical depth shadows with upload conversion
Depth textures previously raw-copied whatever the client handed over
into the Vulkan image, so any client format other than the image's exact
texel layout uploaded garbage (float DEPTH_COMPONENT data read as
16-bit words, GL_TEXTURE_1D/2D alike).

The shadow now has a defined canonical layout - unorm16 for
DEPTH_COMPONENT16, a full-scale unorm32 word for the 24/32-bit fixed
depths, float for DEPTH_COMPONENT32F - produced by the pixel-store
unpack converter (new DepthComponent channel mapping + UNorm32
component). GL_DEPTH_COMPONENT client data may also fill packed
depth-stencil internals (stencil half zero). The Vulkan uploader
converts shadow words to the image texel layout per aspect, and
X8_D24_UNORM falls back to D32_SFLOAT where optimal tiling lacks
support (lavapipe). texture_size_promotion.functional and
packed_depth_stencil.verify_copy_tex_image.* now pass.
2026-08-01 01:15:58 -04:00
BZLZHH f0c0211767 [Fix] (DirectVulkan): raw sRGB attachment writes while FRAMEBUFFER_SRGB is off
GL renders into sRGB color attachments RAW when GL_FRAMEBUFFER_SRGB is
disabled (the core-profile default), but Vulkan sRGB attachments always
encode on write - one decode went missing whenever a rendered-into sRGB
texture was sampled again (multisampled sRGB targets in
texture_size_promotion and texture_swizzle idx27/28 ms cases).

Attachment views (textures and renderbuffers) now reinterpret sRGB
images through their UNORM twin while the capability is off, switching
back when enabled: images get MUTABLE_FORMAT, the attachment-view cache
keys the view format, renderbuffers carry a second view, and the render
pass hash includes the capability state. Sampled views keep decoding.
The VkTextureManager.cpp half of this rides with the next commit.
2026-08-01 01:15:57 -04:00
BZLZHH 9ebbb76df1 [Fix] (DirectVulkan): clamp fixed-point ReadPixels to [0,1]
glReadPixels final conversion honors GL_CLAMP_READ_COLOR (default
GL_FIXED_ONLY): fixed-point normalized color buffers clamp to [0,1] on
read - visible for SNORM attachments, whose negative values previously
leaked through (texture_size_promotion SNORM cases). True float formats
stay unclamped unless the mode is GL_TRUE; GetTexImage is unaffected.
2026-08-01 01:15:57 -04:00
BZLZHH 54a8609c64 [Feat] (DirectVulkan): depth-stencil GetTexImage
The depth-stencil ReadPixels core (per-aspect copies + CPU repack) is
now shared, and glGetTexImage serves GL_DEPTH_COMPONENT /
GL_DEPTH_STENCIL / GL_STENCIL_INDEX queries of depth textures with it
instead of rejecting every non-color aspect
(packed_depth_stencil.verify_get_tex_image.* now passes).
2026-07-31 18:45:52 -04:00
BZLZHH 282dd69230 [Feat] (DirectVulkan, MG_Impl): depth-stencil ReadPixels and combined-attachment queries
glReadPixels now serves GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL and
GL_STENCIL_INDEX from the read framebuffer's depth/stencil attachment:
per-aspect vkCmdCopyImageToBuffer copies (4-byte-aligned stencil region)
with CPU repacking into GL_FLOAT / GL_UNSIGNED_SHORT / GL_UNSIGNED_INT /
GL_UNSIGNED_INT_24_8 / GL_FLOAT_32_UNSIGNED_INT_24_8_REV /
GL_UNSIGNED_BYTE layouts, honoring pack state and pixel-pack buffers.

GL_DEPTH_STENCIL_ATTACHMENT parameter queries follow the spec's combined
rules: differing depth/stencil attachment images (or a lone half) fail
with GL_INVALID_OPERATION, as does GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE
on the combined name. packed_depth_stencil.verify_parameters.* and
verify_read_pixels.depth24_stencil8 now pass.
2026-07-31 18:41:08 -04:00
BZLZHH 8b36a15fb3 [Fix] (DirectVulkan): repair VK_VERIFY varargs and soften image-creation OOM
VK_VERIFY appended the caller's context format string to the base format
while the context ARGUMENTS expanded before the base arguments, so any
failing VK_VERIFY with context args formatted every conversion from the
wrong slot - the %s for VkResultToString dereferenced an integer arg and
crashed inside the logger. The context line is now its own log call
(XXHASH_VERIFY had the same defect).

vmaCreateImage failure in SyncTextureResource is now a soft failure like
the unsupported-sample-count path: a driver may pass the
vkGetPhysicalDeviceImageFormatProperties pre-check yet still refuse
creation (a 4-sample 16K depth texture on lavapipe is 4 GiB), and a GL
implementation must not abort on that.
2026-07-31 18:21:28 -04:00
BZLZHH c069890ac7 [Feat] (DirectVulkan): GPU transform feedback capture via VK_EXT_transform_feedback
Second stage of GL 3.0 transform feedback: captured draws now write real
data.

- Device setup enables the VK_EXT_transform_feedback feature when present
  and loads the bind/begin/end entry points.
- Captured draws compile an XfbCapture program variant whose last
  vertex-processing stage gets XfbBuffer/XfbStride/Offset decorations from
  the program's resolved varyings (a new spirv-opt pass). A captured
  gl_Position is mirrored into a dedicated output written before every
  OpReturn - or before every OpEmitVertex in a geometry stage - ahead of
  the position fixup, so the captured value is the shader's own pre-remap
  position.
- DrawArrays/DrawElements wrap the draw in Begin/EndTransformFeedbackEXT;
  a small counter buffer resumes the append position across draws within
  one glBeginTransformFeedback (fresh Begin starts at the bound offsets).
- Capture targets are promoted to persistently-mapped host-coherent GPU
  storage (persistent-map storage now also carries the transform feedback
  usage), so MapBuffer/GetBufferSubData read the captured bytes after the
  fence wait glEndTransformFeedback now performs.
- Draw-mode/feedback-mode validation defers to the geometry shader's
  output primitive when one is present, and glGetBooleanv reports
  GL_TRANSFORM_FEEDBACK_ACTIVE/PAUSED so dEQP's per-case state reset can
  unwind an active capture.

KHR-GL33: transform_feedback capture_vertex_*/capture_geometry_*/
discard_*/draw_xfb and clip_distance.coverage now pass; queries
(PRIMITIVES_WRITTEN) and gl_ClipDistance capture remain.
2026-07-31 17:31:41 -04:00
BZLZHH 45d506545e [Fix] (MG_Util, DirectVulkan): tolerate storage-less attachments in component-size queries
GetComponentSizesForInternalFormat asserted on TextureInternalFormat::Unknown,
which framebuffer-parameter queries legitimately reach for attachments that
have no storage yet (KHR-GL33.packed_depth_stencil.validate_errors.initial_state
aborted there). Answer with all-zero sizes and keep a warning for genuinely
unhandled formats. Also include the image dimensions in the texture
vmaCreateImage failure report.
2026-07-31 16:35:03 -04:00
BZLZHH 92140405c1 [Feat] (DirectVulkan): emulate GL_LINE_LOOP with closed indexed line strips
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.
2026-07-31 16:25:46 -04:00
BZLZHH b9ecfef0b6 [Fix] (DirectVulkan): handle renderbuffer attachments in color blit clears
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.
2026-07-31 16:15:50 -04:00
BZLZHH fba26ea169 [Fix] (DirectVulkan): round renderbuffer MSAA requests to supported counts
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).
2026-07-31 16:15:50 -04:00
BZLZHH 93a3b55907 [Feat] (DirectVulkan): upload combined depth-stencil texture data
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).
2026-07-31 16:15:50 -04:00
BZLZHH d1487bedf0 [Fix] (DirectVulkan): address array layers in mip upload copies
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.
2026-07-31 16:01:41 -04:00
BZLZHH eb76686c1e [Fix] (DirectVulkan): stop replaying consumed renderbuffer clears mid-pass
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.
2026-07-31 15:51:40 -04:00
BZLZHH 1a04fb8c0c [Fix] (DirectVulkan): support client-memory indices in DrawElements
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.
2026-07-31 15:10:01 -04:00
swung0x48 37111ae992 [Perf] (DirectVulkan): snapshot-gated consecutive-draw fast path skips SetupDraw re-resolution 2026-07-30 09:40:54 -04:00
swung0x48 6b0c2a15ab [Perf] (DirectVulkan): reuse unchanged global-UBO slices and skip identical descriptor binds 2026-07-30 08:18:21 -04:00
swung0x48 e9ffd99313 [Perf] (DirectVulkan): bake the attribute location mask and memoize the explicit-LOD eligibility probe 2026-07-30 08:18:20 -04:00
swung0x48 7c01ddea0c [Perf] (DirectVulkan): drop per-draw weak-ptr locks, re-resolves and rebuilt masks from the sampled-texture and vertex paths 2026-07-30 08:01:07 -04:00
swung0x48 2d4d6e9cfb [Perf] (DirectVulkan): skip pending-clear probes through a lock-free empty check 2026-07-30 07:02:03 -04:00
swung0x48 76b8957b99 [Perf] (DirectVulkan): memoize sampled-texture resources across draws 2026-07-30 07:02:02 -04:00
swung0x48 ec685b9fa7 [Perf] (DirectVulkan): memoize resolved vertex-input state on the VAO and dedupe vertex/index binds 2026-07-30 07:02:01 -04:00
swung0x48 a12068df52 [Perf] (DirectVulkan): reuse pipelines across per-chunk buffers and skip redundant pipeline binds 2026-07-30 05:01:46 -04:00
swung0x48 0b344792cc [Fix] (DirectVulkan): stop fence-waiting out-of-band texture uploads; reclaim transients asynchronously 2026-07-30 05:01:46 -04:00
swung0x48 9fa32bdad0 [Fix] (DirectVulkan): declare only the used colour attachment span per subpass
- 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
2026-07-30 02:45:39 -04:00
swung0x48 a4980f2b56 [Fix] (DirectVulkan): skip redundant per-draw dynamic-state commands
- 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
2026-07-30 02:45:07 -04:00
swung0x48 8ca20e28ca [Fix] (DirectVulkan): size texture backings by their defined mip level count
- 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
2026-07-30 02:43:32 -04:00
swung0x48 c353a2055f [Feat] (DirectVulkan): pre-pass command stream for reorderable out-of-pass work
- 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
2026-07-30 02:43:13 -04:00
swung0x48 421c20984e [Fix] (DirectVulkan): stop loading and carrying dead default-framebuffer content
- 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
2026-07-30 02:40:47 -04:00
swung0x48 fc4cd980f2 [Fix] (DirectVulkan): bound image mutability so Adreno keeps UBWC compression
- 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.
2026-07-29 07:07:50 -04:00
swung0x48 992d16267c [Fix] (DirectVulkan): rewrite implicit-LOD fragment samples to explicit LOD 0 when every bound sampler is pinned to a single mip level - Adreno 650 (driver 512.502) reads outside a full-screen colour render target's allocation on its implicit-LOD sampling path and faults the GPU, which killed MC 26.2 on its own blit shader (texture(InSampler, texCoord)) between frames 344-421 on every run; this is the same driver defect the default-framebuffer blit shader already works around with textureLod, but an application's shader cannot be edited, so ForceExplicitLod0SamplePass converts OpImageSample*ImplicitLod to the explicit form at the SPIR-V level under a new CompileOptionBit that is only requested when the rewrite provably cannot move a texel (every sampler binding on a single-level view, no anisotropy, and either a LOD clamp that already pins lambda at 0 or min and mag filters that agree - an explicit LOD 0 always takes the magnification side of the min/mag decision); a single-level view now also clamps its sampler to mipmapMode NEAREST with maxLod min(maxLod, 0.25) rather than 0, since collapsing the clamp would make every fragment magnify and quietly retire the min filter; and the program's backend hash memo grows from one slot to four so a program resolved under two compile-flag sets in the same frame stops re-hashing every stage's SPIR-V once per draw 2026-07-29 03:26:57 -04:00
swung0x48 0ea9e6de5f [Fix] (DirectVulkan): follow surface resizes instead of rebuilding the swapchain on VK_SUBOPTIMAL_KHR - a per-frame surface-capabilities comparison (ANGLE's model) is now the only thing that schedules a rebuild, so a launcher-side resolution change reaches the swapchain and the compositor scales the smaller image up to the view, while a driver that merely reports the surface as suboptimal can no longer rebuild every frame (each rebuild destroys every pipeline, resets the render-pass manager and reallocates the default framebuffer, which showed as flicker, then corruption, then a crash); the comparison runs in SURFACE space against the extent the live swapchain was created from, since comparing against the swapchain's own quarter-turn-swapped extent reports a difference on every rotated frame 2026-07-28 21:06:57 -04:00
swung0x48 bf312a4b67 [Fix] (DirectVulkan): explicit-LOD blit sampling and present-path hardening - the default-framebuffer blit shader now samples with textureLod 0 (a blit reads exactly the selected level; Adreno 650's implicit-LOD path reads past a single-mip UBWC render target's allocation despite maxLod=0, page-faulting the GPU on MC 26.2's second startup frame once the neighbouring startup staging memory is returned - the invalidated context then failed the next Present submit with EDEADLK/DEVICE_LOST), TransitionToPresent appends the present barrier into the frame's open recording instead of silently dropping it whenever anything was recorded (frames without a default-FBO render pass presented images stuck in their acquired layout), VK_SUBOPTIMAL_KHR acquires are treated as the success they are (image acquired, semaphore signal armed - the early return skipped the fence reset and consumed-flag clear, and callers re-acquired on the same binary semaphore; rebuilds now defer to after the signal is consumed), and validation builds report through VK_EXT_debug_report when VK_EXT_debug_utils is absent instead of aborting instance creation 2026-07-28 06:00:20 -04:00
swung0x48 8a0a8a0274 [Fix] (DirectVulkan): harden the leak-fix round after adversarial review - pipeline memo now drops at every command-buffer boundary (a flush-loop-memoized pipeline could age out and be destroyed while its submission was in flight), mid-frame drains no longer rewind the arena or advance the cache-aging clocks in presenting apps (gated to every 8th drain since the last Present, so readback/fence-heavy frames neither churn conversions nor shrink the 1024-boundary retire window), render-pass eviction notifies the pipeline cache once per sweep batch instead of once per dying pass, descriptor pools use FREE_DESCRIPTOR_SET_BIT so a destroyed layout's cached sets are freed back and credited instead of abandoning pool slots (the live-layout age sweep that could orphan slots is removed - layout destruction is the sole purge path), and renderbuffer respecify parks the old backing for aged destruction instead of destroying it while possibly in flight 2026-07-27 22:51:26 -04:00
swung0x48 d076c29146 [Fix] (DirectVulkan): bound the vertex-input and sampler caches and sweep undeleted GL syncs - both caches age out entries idle >1024 frame boundaries (animated LOD bias no longer mints a VkSampler per float value, buffer/VAO churn no longer grows the vertex-input map for the whole session), and library teardown drains the live-sync registry exactly as glDeleteSync would since GL requires syncs to die with their context 2026-07-27 22:16:16 -04:00
swung0x48 930a607bdf [Fix] (DirectVulkan): make texture/renderbuffer GC reach every dead resource - name-deleted textures register via weak_from_this so first-sync-after-delete can no longer orphan a TextureResource, an orphan sweep makes GC authoritative over the resource map, dead-texture pruning moves to a frame-boundary gate (64 frames) so churn through clears/readbacks reclaims without draws, and dead renderbuffers age past frames-in-flight before their VkImage/view is destroyed instead of leaking until shutdown (or being freed while in flight) 2026-07-27 22:16:15 -04:00
swung0x48 34685b4bb0 [Fix] (DirectVulkan): age-based eviction for the content-addressed cache family - ProgramFactory entries (shader modules/layouts), PipelineFactory graphics pipelines, compute pipelines and per-layout descriptor-set tracking now retire after ~1024 idle frame boundaries (render-pass-manager sweep precedent), render-pass eviction purges pipelines hashed on the dying handle (closes a handle-recycling stale-pipeline hazard), and the program reflection cache is lifetime-id-keyed and cleared at EGL teardown - shader/program churn no longer grows Vulkan objects without bound 2026-07-27 22:05:25 -04:00
swung0x48 c540fb88ee [Fix] (DirectVulkan): drain frame transients on present-less paths - readback waits, suspended presentation, blocking sync waits and flush completion polls now run Present's per-frame drains (deferred buffer/texture releases, transient arena rewind, descriptor cursors, retired command buffers, conversion caches) whenever every submission is provably complete, so offscreen/minimized workloads stay bounded; never blocks, frames-in-flight overlap untouched 2026-07-27 21:45:21 -04:00
swung0x48 7e048fc2bf [Fix] (DirectVulkan): map RGB10_A2(UI) to A2B10G10R10 - GL 2_10_10_10_REV puts R in bits 0-9 so the A2R10G10B10 mapping silently swapped R/B on upload; also decode both 1010102 variants in readback 2026-07-26 19:13:46 -04:00