mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 20:58:31 +09:00
[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:
@@ -2988,6 +2988,8 @@ void main() {
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||
s_vkCmdDrawMultiEXT = nullptr;
|
||||
s_vkCmdDrawMultiIndexedEXT = nullptr;
|
||||
|
||||
if (m_instance != VK_NULL_HANDLE && m_surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
@@ -9562,14 +9564,191 @@ void main() {
|
||||
|
||||
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
|
||||
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
EmitMultiDraw(frame.commandBuffer, payload.pParams, payload.drawCount);
|
||||
}
|
||||
|
||||
for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) {
|
||||
vkCmdDraw(commandBuffer,
|
||||
payload.pParams[idraw].vertexCount,
|
||||
payload.pParams[idraw].instanceCount,
|
||||
payload.pParams[idraw].firstVertex,
|
||||
payload.pParams[idraw].firstInstance);
|
||||
// The tier-2 indirect batch uploads the param arrays as-is: the leading members of the
|
||||
// renderer's draw-parameter structs are exactly Vulkan's indirect command layouts, and
|
||||
// vkCmdDraw(Indexed)Indirect accepts any 4-aligned stride >= the command size, so the
|
||||
// trailing CPU-side metadata rides along unread instead of forcing a repack.
|
||||
static_assert(sizeof(DrawIndexedCmdParam) == sizeof(VkDrawIndexedIndirectCommand) &&
|
||||
offsetof(DrawIndexedCmdParam, indexCount) == offsetof(VkDrawIndexedIndirectCommand, indexCount) &&
|
||||
offsetof(DrawIndexedCmdParam, instanceCount) ==
|
||||
offsetof(VkDrawIndexedIndirectCommand, instanceCount) &&
|
||||
offsetof(DrawIndexedCmdParam, firstIndex) == offsetof(VkDrawIndexedIndirectCommand, firstIndex) &&
|
||||
offsetof(DrawIndexedCmdParam, vertexOffset) ==
|
||||
offsetof(VkDrawIndexedIndirectCommand, vertexOffset) &&
|
||||
offsetof(DrawIndexedCmdParam, firstInstance) ==
|
||||
offsetof(VkDrawIndexedIndirectCommand, firstInstance),
|
||||
"DrawIndexedCmdParam must alias VkDrawIndexedIndirectCommand for the tier-2 multi-draw upload");
|
||||
static_assert(sizeof(DrawCmdParam) % 4 == 0 && sizeof(DrawCmdParam) >= sizeof(VkDrawIndirectCommand) &&
|
||||
offsetof(DrawCmdParam, vertexCount) == offsetof(VkDrawIndirectCommand, vertexCount) &&
|
||||
offsetof(DrawCmdParam, instanceCount) == offsetof(VkDrawIndirectCommand, instanceCount) &&
|
||||
offsetof(DrawCmdParam, firstVertex) == offsetof(VkDrawIndirectCommand, firstVertex) &&
|
||||
offsetof(DrawCmdParam, firstInstance) == offsetof(VkDrawIndirectCommand, firstInstance),
|
||||
"DrawCmdParam must lead with VkDrawIndirectCommand for the tier-2 multi-draw upload");
|
||||
|
||||
void VulkanRenderer::EmitMultiDraw(VkCommandBuffer commandBuffer, const DrawCmdParam* pParams, Uint32 drawCount) {
|
||||
if (drawCount == 0) {
|
||||
return;
|
||||
}
|
||||
if (drawCount == 1) {
|
||||
vkCmdDraw(commandBuffer, pParams[0].vertexCount, pParams[0].instanceCount, pParams[0].firstVertex,
|
||||
pParams[0].firstInstance);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tier 1: VK_EXT_multi_draw. vkCmdDrawMultiEXT shares one instanceCount/firstInstance
|
||||
// across the whole batch, so the batch must be uniform in both (GL's glMultiDrawArrays
|
||||
// always is: 1/0).
|
||||
if (m_multiDrawAllowExt) {
|
||||
Bool uniformInstances = true;
|
||||
for (Uint32 idraw = 1; idraw < drawCount; ++idraw) {
|
||||
if (pParams[idraw].instanceCount != pParams[0].instanceCount ||
|
||||
pParams[idraw].firstInstance != pParams[0].firstInstance) {
|
||||
uniformInstances = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uniformInstances) {
|
||||
static Vector<VkMultiDrawInfoEXT> infos;
|
||||
infos.resize(drawCount);
|
||||
for (Uint32 idraw = 0; idraw < drawCount; ++idraw) {
|
||||
infos[idraw].firstVertex = pParams[idraw].firstVertex;
|
||||
infos[idraw].vertexCount = pParams[idraw].vertexCount;
|
||||
}
|
||||
for (Uint32 base = 0; base < drawCount; base += m_maxMultiDrawCount) {
|
||||
const Uint32 chunk = std::min(drawCount - base, m_maxMultiDrawCount);
|
||||
s_vkCmdDrawMultiEXT(commandBuffer, chunk, infos.data() + base, pParams[0].instanceCount,
|
||||
pParams[0].firstInstance, sizeof(VkMultiDrawInfoEXT));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 2: multiDrawIndirect - one vkCmdDrawIndirect over a transient command array.
|
||||
// A sub-draw with firstInstance != 0 is illegal in an indirect command without the
|
||||
// drawIndirectFirstInstance feature; such a batch falls to the unrolled tier.
|
||||
if (m_multiDrawAllowIndirect) {
|
||||
Bool firstInstanceLegal = m_drawIndirectFirstInstanceFeatureEnabled;
|
||||
if (!firstInstanceLegal) {
|
||||
firstInstanceLegal = true;
|
||||
for (Uint32 idraw = 0; idraw < drawCount; ++idraw) {
|
||||
if (pParams[idraw].firstInstance != 0) {
|
||||
firstInstanceLegal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const Uint32 maxIndirectCount = m_physicalDevice.properties.limits.maxDrawIndirectCount;
|
||||
if (firstInstanceLegal && maxIndirectCount > 0) {
|
||||
BufferSlice commandSlice{};
|
||||
if (m_bufferManager.UploadTransient(BufferKind::Indirect, m_frameContext.GetCurrentFrameIndex(),
|
||||
pParams,
|
||||
static_cast<VkDeviceSize>(drawCount) * sizeof(DrawCmdParam),
|
||||
sizeof(Uint32), commandSlice)) {
|
||||
for (Uint32 base = 0; base < drawCount; base += maxIndirectCount) {
|
||||
const Uint32 chunk = std::min(drawCount - base, maxIndirectCount);
|
||||
vkCmdDrawIndirect(commandBuffer, commandSlice.buffer,
|
||||
commandSlice.offset +
|
||||
static_cast<VkDeviceSize>(base) * sizeof(DrawCmdParam),
|
||||
chunk, sizeof(DrawCmdParam));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Transient arena refused the upload: fall through to the unrolled tier.
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: unrolled loop, byte-identical fallback (and the only tier where a SPIR-V
|
||||
// DrawIndex consumer sees 0 for every sub-draw instead of the sub-draw index).
|
||||
for (Uint32 idraw = 0; idraw < drawCount; ++idraw) {
|
||||
vkCmdDraw(commandBuffer, pParams[idraw].vertexCount, pParams[idraw].instanceCount,
|
||||
pParams[idraw].firstVertex, pParams[idraw].firstInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::EmitMultiDrawIndexed(VkCommandBuffer commandBuffer, const DrawIndexedCmdParam* pParams,
|
||||
Uint32 drawCount) {
|
||||
if (drawCount == 0) {
|
||||
return;
|
||||
}
|
||||
if (drawCount == 1) {
|
||||
vkCmdDrawIndexed(commandBuffer, pParams[0].indexCount, pParams[0].instanceCount, pParams[0].firstIndex,
|
||||
pParams[0].vertexOffset, pParams[0].firstInstance);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tier 1: VK_EXT_multi_draw. VkMultiDrawIndexedInfoEXT carries per-draw
|
||||
// firstIndex/indexCount/vertexOffset (pVertexOffset = nullptr keeps the per-draw
|
||||
// offsets), but instanceCount/firstInstance are batch-wide, so the batch must be
|
||||
// uniform in both (GL's glMultiDrawElements* always is: 1/0).
|
||||
if (m_multiDrawAllowExt) {
|
||||
Bool uniformInstances = true;
|
||||
for (Uint32 idraw = 1; idraw < drawCount; ++idraw) {
|
||||
if (pParams[idraw].instanceCount != pParams[0].instanceCount ||
|
||||
pParams[idraw].firstInstance != pParams[0].firstInstance) {
|
||||
uniformInstances = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uniformInstances) {
|
||||
static Vector<VkMultiDrawIndexedInfoEXT> infos;
|
||||
infos.resize(drawCount);
|
||||
for (Uint32 idraw = 0; idraw < drawCount; ++idraw) {
|
||||
infos[idraw].firstIndex = pParams[idraw].firstIndex;
|
||||
infos[idraw].indexCount = pParams[idraw].indexCount;
|
||||
infos[idraw].vertexOffset = pParams[idraw].vertexOffset;
|
||||
}
|
||||
for (Uint32 base = 0; base < drawCount; base += m_maxMultiDrawCount) {
|
||||
const Uint32 chunk = std::min(drawCount - base, m_maxMultiDrawCount);
|
||||
s_vkCmdDrawMultiIndexedEXT(commandBuffer, chunk, infos.data() + base,
|
||||
pParams[0].instanceCount,
|
||||
static_cast<Uint32>(pParams[0].firstInstance),
|
||||
sizeof(VkMultiDrawIndexedInfoEXT), nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 2: multiDrawIndirect - one vkCmdDrawIndexedIndirect over a transient command
|
||||
// array (DrawIndexedCmdParam aliases VkDrawIndexedIndirectCommand, see static_assert).
|
||||
if (m_multiDrawAllowIndirect) {
|
||||
Bool firstInstanceLegal = m_drawIndirectFirstInstanceFeatureEnabled;
|
||||
if (!firstInstanceLegal) {
|
||||
firstInstanceLegal = true;
|
||||
for (Uint32 idraw = 0; idraw < drawCount; ++idraw) {
|
||||
if (pParams[idraw].firstInstance != 0) {
|
||||
firstInstanceLegal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const Uint32 maxIndirectCount = m_physicalDevice.properties.limits.maxDrawIndirectCount;
|
||||
if (firstInstanceLegal && maxIndirectCount > 0) {
|
||||
BufferSlice commandSlice{};
|
||||
if (m_bufferManager.UploadTransient(BufferKind::Indirect, m_frameContext.GetCurrentFrameIndex(),
|
||||
pParams,
|
||||
static_cast<VkDeviceSize>(drawCount) *
|
||||
sizeof(DrawIndexedCmdParam),
|
||||
sizeof(Uint32), commandSlice)) {
|
||||
for (Uint32 base = 0; base < drawCount; base += maxIndirectCount) {
|
||||
const Uint32 chunk = std::min(drawCount - base, maxIndirectCount);
|
||||
vkCmdDrawIndexedIndirect(commandBuffer, commandSlice.buffer,
|
||||
commandSlice.offset +
|
||||
static_cast<VkDeviceSize>(base) * sizeof(DrawIndexedCmdParam),
|
||||
chunk, sizeof(DrawIndexedCmdParam));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: unrolled loop, byte-identical fallback (and the only tier where a SPIR-V
|
||||
// DrawIndex consumer sees 0 for every sub-draw instead of the sub-draw index).
|
||||
for (Uint32 idraw = 0; idraw < drawCount; ++idraw) {
|
||||
vkCmdDrawIndexed(commandBuffer, pParams[idraw].indexCount, pParams[idraw].instanceCount,
|
||||
pParams[idraw].firstIndex, pParams[idraw].vertexOffset, pParams[idraw].firstInstance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9591,9 +9770,9 @@ void main() {
|
||||
|
||||
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
|
||||
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
|
||||
// Collapse contiguous sub-draw runs into one vkCmdDrawIndexed. Per-sub-draw
|
||||
// Collapse contiguous sub-draw runs BEFORE tier dispatch: merging shrinks the
|
||||
// param span every tier consumes (fewer VkMultiDrawIndexedInfoEXT entries, a
|
||||
// smaller transient command array, fewer unrolled vkCmdDrawIndexed). Per-sub-draw
|
||||
// command emission in the driver dominates a Sodium-shaped multi-draw
|
||||
// (steady-state profile: >60% of the case inside the Vulkan driver's
|
||||
// vkCmdDrawIndexed encoding for 132x32 sub-draws/frame), and a chunk
|
||||
@@ -9622,47 +9801,45 @@ void main() {
|
||||
mergeGranularity = 0;
|
||||
}
|
||||
}
|
||||
if (mergeGranularity == 0) {
|
||||
for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) {
|
||||
vkCmdDrawIndexed(commandBuffer,
|
||||
payload.pParams[idraw].indexCount,
|
||||
payload.pParams[idraw].instanceCount,
|
||||
payload.pParams[idraw].firstIndex,
|
||||
payload.pParams[idraw].vertexOffset,
|
||||
payload.pParams[idraw].firstInstance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Uint32 idraw = 0;
|
||||
while (idraw < payload.drawCount) {
|
||||
const DrawIndexedCmdParam& head = payload.pParams[idraw];
|
||||
++idraw;
|
||||
if (head.indexCount == 0) {
|
||||
continue; // draws nothing, contributes nothing to a run
|
||||
}
|
||||
Uint32 mergedIndexCount = head.indexCount;
|
||||
if (head.instanceCount == 1) {
|
||||
while (idraw < payload.drawCount) {
|
||||
const DrawIndexedCmdParam& next = payload.pParams[idraw];
|
||||
if (next.indexCount == 0) {
|
||||
++idraw;
|
||||
continue;
|
||||
}
|
||||
if (mergedIndexCount % mergeGranularity != 0 ||
|
||||
next.instanceCount != 1 ||
|
||||
next.vertexOffset != head.vertexOffset ||
|
||||
next.firstInstance != head.firstInstance ||
|
||||
next.firstIndex != head.firstIndex + mergedIndexCount ||
|
||||
mergedIndexCount + next.indexCount < mergedIndexCount) {
|
||||
break;
|
||||
}
|
||||
mergedIndexCount += next.indexCount;
|
||||
++idraw;
|
||||
const DrawIndexedCmdParam* pParams = payload.pParams;
|
||||
Uint32 drawCount = payload.drawCount;
|
||||
static Vector<DrawIndexedCmdParam> mergedParams;
|
||||
if (mergeGranularity != 0) {
|
||||
mergedParams.clear();
|
||||
mergedParams.reserve(drawCount);
|
||||
Uint32 idraw = 0;
|
||||
while (idraw < drawCount) {
|
||||
DrawIndexedCmdParam head = pParams[idraw];
|
||||
++idraw;
|
||||
if (head.indexCount == 0) {
|
||||
continue; // draws nothing, contributes nothing to a run
|
||||
}
|
||||
if (head.instanceCount == 1) {
|
||||
while (idraw < drawCount) {
|
||||
const DrawIndexedCmdParam& next = pParams[idraw];
|
||||
if (next.indexCount == 0) {
|
||||
++idraw;
|
||||
continue;
|
||||
}
|
||||
if (head.indexCount % mergeGranularity != 0 ||
|
||||
next.instanceCount != 1 ||
|
||||
next.vertexOffset != head.vertexOffset ||
|
||||
next.firstInstance != head.firstInstance ||
|
||||
next.firstIndex != head.firstIndex + head.indexCount ||
|
||||
head.indexCount + next.indexCount < head.indexCount) {
|
||||
break;
|
||||
}
|
||||
head.indexCount += next.indexCount;
|
||||
++idraw;
|
||||
}
|
||||
}
|
||||
mergedParams.push_back(head);
|
||||
}
|
||||
vkCmdDrawIndexed(commandBuffer, mergedIndexCount, head.instanceCount, head.firstIndex,
|
||||
head.vertexOffset, head.firstInstance);
|
||||
pParams = mergedParams.data();
|
||||
drawCount = static_cast<Uint32>(mergedParams.size());
|
||||
}
|
||||
|
||||
EmitMultiDrawIndexed(frame.commandBuffer, pParams, drawCount);
|
||||
}
|
||||
|
||||
// Byte size of the command structures GL defines for the indirect draws (GL 4.6 core
|
||||
@@ -9838,7 +10015,8 @@ void main() {
|
||||
|
||||
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
|
||||
MGLOG_D("DirectVulkan: glMultiDrawElementsIndirect(drawcount=%d stride=%d)", drawcount, stride);
|
||||
if (drawcount == 1 || (m_multiDrawIndirectFeatureEnabled && stride % 4 == 0)) {
|
||||
if (drawcount == 1 ||
|
||||
(!m_multiDrawForceUnrollIndirect && m_multiDrawIndirectFeatureEnabled && stride % 4 == 0)) {
|
||||
vkCmdDrawIndexedIndirect(frame.commandBuffer,
|
||||
drawSlice.buffer,
|
||||
drawSlice.offset + static_cast<VkDeviceSize>(commandOffset),
|
||||
@@ -9901,7 +10079,8 @@ void main() {
|
||||
|
||||
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
|
||||
MGLOG_D("DirectVulkan: glMultiDrawArraysIndirect(drawcount=%d stride=%d)", drawcount, stride);
|
||||
if (drawcount == 1 || (m_multiDrawIndirectFeatureEnabled && stride % 4 == 0)) {
|
||||
if (drawcount == 1 ||
|
||||
(!m_multiDrawForceUnrollIndirect && m_multiDrawIndirectFeatureEnabled && stride % 4 == 0)) {
|
||||
vkCmdDrawIndirect(frame.commandBuffer,
|
||||
drawSlice.buffer,
|
||||
drawSlice.offset + static_cast<VkDeviceSize>(commandOffset),
|
||||
@@ -11087,6 +11266,7 @@ void main() {
|
||||
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat);
|
||||
}
|
||||
deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance;
|
||||
m_drawIndirectFirstInstanceFeatureEnabled = deviceFeatures.drawIndirectFirstInstance == VK_TRUE;
|
||||
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
||||
m_multiDrawIndirectFeatureEnabled = deviceFeatures.multiDrawIndirect == VK_TRUE;
|
||||
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
|
||||
@@ -11368,6 +11548,56 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
// VK_EXT_multi_draw: tier 1 of the multi-draw dispatch - one vkCmdDrawMulti(Indexed)EXT
|
||||
// for a whole glMultiDraw* batch (VkMultiDrawIndexedInfoEXT carries per-draw
|
||||
// firstIndex/indexCount/vertexOffset, so glMultiDrawElementsBaseVertex fits natively).
|
||||
// Requested only when both the extension and its multiDraw feature are present;
|
||||
// absent it, the dispatch falls to the multiDrawIndirect tier or the unrolled loop.
|
||||
m_multiDrawExtensionEnabled = false;
|
||||
m_maxMultiDrawCount = 0;
|
||||
VkPhysicalDeviceMultiDrawFeaturesEXT multiDrawFeatures{};
|
||||
multiDrawFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT;
|
||||
if (IsExtensionSupported(availableExtensions, VK_EXT_MULTI_DRAW_EXTENSION_NAME) &&
|
||||
getPhysicalDeviceFeatures2 != nullptr) {
|
||||
VkPhysicalDeviceFeatures2 featureQuery{};
|
||||
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
featureQuery.pNext = &multiDrawFeatures;
|
||||
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
||||
if (multiDrawFeatures.multiDraw == VK_TRUE) {
|
||||
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_MULTI_DRAW_EXTENSION_NAME)) {
|
||||
enabledDeviceExtensions.push_back(VK_EXT_MULTI_DRAW_EXTENSION_NAME);
|
||||
}
|
||||
multiDrawFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
||||
deviceCreateInfo.pNext = &multiDrawFeatures;
|
||||
m_multiDrawExtensionEnabled = true;
|
||||
|
||||
VkPhysicalDeviceMultiDrawPropertiesEXT multiDrawProperties{};
|
||||
multiDrawProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_PROPERTIES_EXT;
|
||||
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
|
||||
if (getPhysicalDeviceProperties2 == nullptr) {
|
||||
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
|
||||
}
|
||||
if (getPhysicalDeviceProperties2 != nullptr) {
|
||||
VkPhysicalDeviceProperties2 propertyQuery{};
|
||||
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
||||
propertyQuery.pNext = &multiDrawProperties;
|
||||
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
||||
}
|
||||
// Spec minimum is 1024; a driver reporting 0 through a failed query must not
|
||||
// zero out every batch, so fall back to the spec minimum.
|
||||
m_maxMultiDrawCount = multiDrawProperties.maxMultiDrawCount != 0
|
||||
? multiDrawProperties.maxMultiDrawCount
|
||||
: 1024;
|
||||
MGLOG_I("Enabled optional device extension: %s (maxMultiDrawCount=%u)",
|
||||
VK_EXT_MULTI_DRAW_EXTENSION_NAME, m_maxMultiDrawCount);
|
||||
} else {
|
||||
MGLOG_I("VK_EXT_multi_draw is advertised but its multiDraw feature is unavailable; "
|
||||
"multi-draw batches use the indirect or unrolled tier");
|
||||
}
|
||||
}
|
||||
|
||||
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
||||
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
||||
@@ -11424,6 +11654,51 @@ void main() {
|
||||
m_drawIndirectCountExtensionEnabled = false;
|
||||
}
|
||||
|
||||
s_vkCmdDrawMultiEXT = nullptr;
|
||||
s_vkCmdDrawMultiIndexedEXT = nullptr;
|
||||
if (m_multiDrawExtensionEnabled) {
|
||||
s_vkCmdDrawMultiEXT =
|
||||
reinterpret_cast<PFN_vkCmdDrawMultiEXT>(vkGetDeviceProcAddr(m_device, "vkCmdDrawMultiEXT"));
|
||||
s_vkCmdDrawMultiIndexedEXT = reinterpret_cast<PFN_vkCmdDrawMultiIndexedEXT>(
|
||||
vkGetDeviceProcAddr(m_device, "vkCmdDrawMultiIndexedEXT"));
|
||||
if (s_vkCmdDrawMultiEXT == nullptr || s_vkCmdDrawMultiIndexedEXT == nullptr) {
|
||||
MGLOG_W("VK_EXT_multi_draw enabled but its entry points are missing, will continue as if "
|
||||
"VK_EXT_multi_draw is not supported!");
|
||||
s_vkCmdDrawMultiEXT = nullptr;
|
||||
s_vkCmdDrawMultiIndexedEXT = nullptr;
|
||||
m_multiDrawExtensionEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the multi-draw dispatch tiers once: device support clamped by the
|
||||
// MOBILEGL_MAGMA_MULTIDRAW_MODE preference. Requesting an unavailable tier is
|
||||
// never an error - the dispatch falls down the chain ext -> indirect -> unroll.
|
||||
{
|
||||
using MG_Config::MultiDrawMode;
|
||||
const MultiDrawMode mode = MG_Config::Features.MagmaMultiDrawMode;
|
||||
m_multiDrawAllowExt =
|
||||
m_multiDrawExtensionEnabled && (mode == MultiDrawMode::Auto || mode == MultiDrawMode::Ext);
|
||||
m_multiDrawAllowIndirect = m_multiDrawIndirectFeatureEnabled && mode != MultiDrawMode::Unroll;
|
||||
m_multiDrawForceUnrollIndirect = mode == MultiDrawMode::Unroll;
|
||||
if (mode == MultiDrawMode::Ext && !m_multiDrawExtensionEnabled) {
|
||||
MGLOG_I("MOBILEGL_MAGMA_MULTIDRAW_MODE=ext requested but VK_EXT_multi_draw is unavailable; "
|
||||
"falling back to the %s tier",
|
||||
m_multiDrawAllowIndirect ? "indirect" : "unroll");
|
||||
}
|
||||
if (mode == MultiDrawMode::Indirect && !m_multiDrawIndirectFeatureEnabled) {
|
||||
MGLOG_I("MOBILEGL_MAGMA_MULTIDRAW_MODE=indirect requested but the multiDrawIndirect device "
|
||||
"feature is unavailable; falling back to the unroll tier");
|
||||
}
|
||||
MGLOG_I("Multi-draw dispatch tier: %s (VK_EXT_multi_draw=%s, multiDrawIndirect=%s, mode=%s)",
|
||||
m_multiDrawAllowExt ? "ext" : (m_multiDrawAllowIndirect ? "indirect" : "unroll"),
|
||||
m_multiDrawExtensionEnabled ? "true" : "false",
|
||||
m_multiDrawIndirectFeatureEnabled ? "true" : "false",
|
||||
mode == MultiDrawMode::Auto ? "auto"
|
||||
: mode == MultiDrawMode::Ext ? "ext"
|
||||
: mode == MultiDrawMode::Indirect ? "indirect"
|
||||
: "unroll");
|
||||
}
|
||||
|
||||
if (m_transformFeedbackFeatureEnabled) {
|
||||
s_vkCmdBindTransformFeedbackBuffersEXT = reinterpret_cast<PFN_vkCmdBindTransformFeedbackBuffersEXT>(
|
||||
vkGetDeviceProcAddr(m_device, "vkCmdBindTransformFeedbackBuffersEXT"));
|
||||
|
||||
Reference in New Issue
Block a user