[Fix, Feat] (MG_Backend): make DirectVulkan multi-draw actually draw, then pick its best tier

The bug: DirectVulkan.cpp::MultiDrawElements had its entire body
commented out - plain glMultiDrawElements on Magma recorded NOTHING,
no error, no pixels (readback shows the deferred clear never even
materialized). It now shares the tuned base-vertex implementation, and
both plain entries are pixel-proven by a 4-sub-draw harness.

The feature: every CPU-side multi-draw form dispatches through three
tiers after round-9's contiguous-run merge (restructured to merge into
a span BEFORE dispatch, so every tier consumes the shrunken array):
  1. VK_EXT_multi_draw: one vkCmdDrawMulti(Indexed)EXT, chunked by
     maxMultiDrawCount; per-draw vertexOffset rides in the struct. The
     extension is requested only when enumerated and its feature bit
     confirmed, entry points via vkGetDeviceProcAddr, demoted if
     missing.
  2. multiDrawIndirect: the param span uploads DIRECTLY as a transient
     INDIRECT-usage buffer - DrawIndexedCmdParam is layout-identical
     to VkDrawIndexedIndirectCommand and DrawCmdParam's head is a
     legal 24-byte-stride VkDrawIndirectCommand, both static_asserted,
     so no repacking - then one vkCmdDraw(Indexed)Indirect per
     maxDrawIndirectCount chunk. firstInstance!=0 additionally
     requires drawIndirectFirstInstance or the batch drops a tier.
  3. The byte-identical unroll.
gl_DrawID: tiers 1-2 are spec-correct (0,1,2,3 across a probe's
sub-draws); the unroll tier keeps the pre-existing always-0 contract.
The default tiers strictly improve DrawID correctness.

Adversarially verified: the five real DirectVulkan retrace images are
BIT-IDENTICAL (md5) across auto/ext/indirect/unroll; zero validation
VUIDs on every tier; a simulated no-EXT device resolves to indirect
and renders the same bytes; the known-red create-indirect fixture
crashes at the identical call before and after (not worse, not fixed).
Unit suite 423/423 on the rebased tree, retrace subset 10/10. Bench:
mc_sodium_multidraw's contiguous shape merges 32->1 before dispatch,
so no bench delta - the tiers' beneficiaries are non-contiguous real
streams (the sodium RETRACE pushes ~58-sub-draw batches, in=out
243101 with zero merges) and mobile drivers. A reproducible +3-4%
code-layout drift on mc_use_program (zero shared code, I-cache
displacement from +400 lines) stays under the action gate and is
booked here rather than hidden.
This commit is contained in:
BZLZHH
2026-08-07 06:41:21 -04:00
parent 231d5c90e4
commit ebc5bff9b1
3 changed files with 416 additions and 117 deletions
@@ -327,6 +327,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool RecreateSwapchain();
private:
// Tiered emission for an already-set-up multi-draw batch (state bound, index
// buffer bound for the indexed form). Tier 1: VK_EXT_multi_draw. Tier 2: one
// vkCmdDraw(Indexed)Indirect over a transient command array. Tier 3: unrolled
// vkCmdDraw(Indexed) loop. Tier eligibility is per-batch (uniform instance
// state for tier 1, firstInstance/feature legality for tier 2); every tier
// consumes the same param span, so contiguous-run merging done by the caller
// benefits all of them.
void EmitMultiDrawIndexed(VkCommandBuffer commandBuffer, const DrawIndexedCmdParam* pParams, Uint32 drawCount);
void EmitMultiDraw(VkCommandBuffer commandBuffer, const DrawCmdParam* pParams, Uint32 drawCount);
struct BlitUniformData {
float srcRect[4] = {0.f, 0.f, 1.f, 1.f};
float dstRect[4] = {0.f, 0.f, 1.f, 1.f};
@@ -477,6 +487,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool m_indexTypeUint8ExtensionEnabled = false;
Bool m_logicOpFeatureEnabled = false;
Bool m_multiDrawIndirectFeatureEnabled = false;
// drawIndirectFirstInstance gates indirect commands whose firstInstance != 0;
// cached at device creation because the tier-2 multi-draw path (a transient
// VkDrawIndexedIndirectCommand array) is illegal for such a sub-draw without it.
Bool m_drawIndirectFirstInstanceFeatureEnabled = false;
// VK_EXT_multi_draw: native batched submission for the CPU-side glMultiDraw*
// families (tier 1 of the multi-draw dispatch).
Bool m_multiDrawExtensionEnabled = false;
Uint32 m_maxMultiDrawCount = 0;
// Multi-draw dispatch tiers, resolved once at device creation from device support
// clamped by MOBILEGL_MAGMA_MULTIDRAW_MODE (a preference, never a demand):
// tier 1 (ext): one vkCmdDrawMulti(Indexed)EXT - m_multiDrawAllowExt
// tier 2 (indirect): one vkCmdDraw(Indexed)Indirect batch - m_multiDrawAllowIndirect
// tier 3 (unroll): one vkCmdDraw(Indexed) per sub-draw - always available
// m_multiDrawForceUnrollIndirect additionally forces the GPU-parameter
// glMultiDraw*Indirect paths onto their per-command loop (mode=unroll only).
Bool m_multiDrawAllowExt = false;
Bool m_multiDrawAllowIndirect = false;
Bool m_multiDrawForceUnrollIndirect = false;
Bool m_samplerAnisotropyFeatureEnabled = false;
Bool m_shaderDrawParametersExtensionEnabled = false;
Bool m_shaderDrawParametersFeatureEnabled = false;
@@ -508,6 +536,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
Uint32 stride);
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
// VK_EXT_multi_draw entry points, loaded at device creation when the extension
// (and its multiDraw feature) is enabled; null otherwise.
static inline PFN_vkCmdDrawMultiEXT s_vkCmdDrawMultiEXT = nullptr;
static inline PFN_vkCmdDrawMultiIndexedEXT s_vkCmdDrawMultiIndexedEXT = nullptr;
// VK_EXT_transform_feedback (GL transform feedback capture)
Bool m_transformFeedbackFeatureEnabled = false;