[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.
This commit is contained in:
BZLZHH
2026-08-05 10:04:30 -04:00
parent 0e7692251d
commit c8c7b19579
5 changed files with 227 additions and 0 deletions
+50
View File
@@ -1448,6 +1448,56 @@ namespace MobileGL::MG_Util::SelfTest {
"unavailable; shaders using gl_DrawID/gl_BaseInstance will not work");
}
Bool provokingVertexLast = false;
Bool transformFeedbackPreservesProvokingVertex = false;
Bool provokingVertexModePerPipeline = false;
Bool transformFeedbackPreservesTriangleFanProvokingVertex = false;
if (vkGetPhysicalDeviceFeatures2Fn != nullptr &&
HasVkExtension(deviceExtensions, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME)) {
VkPhysicalDeviceProvokingVertexFeaturesEXT provokingVertexFeatures{};
provokingVertexFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT;
VkPhysicalDeviceFeatures2 features2{};
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features2.pNext = &provokingVertexFeatures;
vkGetPhysicalDeviceFeatures2Fn(physicalDevice, &features2);
provokingVertexLast = provokingVertexFeatures.provokingVertexLast == VK_TRUE;
transformFeedbackPreservesProvokingVertex =
provokingVertexFeatures.transformFeedbackPreservesProvokingVertex == VK_TRUE;
if (vkGetPhysicalDeviceProperties2Fn != nullptr) {
VkPhysicalDeviceProvokingVertexPropertiesEXT provokingVertexProperties{};
provokingVertexProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_PROPERTIES_EXT;
VkPhysicalDeviceProperties2 properties2{};
properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
properties2.pNext = &provokingVertexProperties;
vkGetPhysicalDeviceProperties2Fn(physicalDevice, &properties2);
provokingVertexModePerPipeline =
provokingVertexProperties.provokingVertexModePerPipeline == VK_TRUE;
transformFeedbackPreservesTriangleFanProvokingVertex =
provokingVertexProperties.transformFeedbackPreservesTriangleFanProvokingVertex == VK_TRUE;
}
}
if (provokingVertexLast) {
builder.Pass("provokingVertexLast",
"supported; flat varyings take GL's last vertex and transform feedback records "
"strip/fan triangles in GL's vertex order");
} else {
builder.Warn("provokingVertexLast",
"unsupported; flat-shaded varyings take a primitive's first vertex instead of GL's "
"last, and transform feedback records TRIANGLE_STRIP/TRIANGLE_FAN triangles rotated "
"(e.g. 0,1,2 / 1,3,2 instead of 0,1,2 / 2,1,3)");
}
if (provokingVertexLast && !transformFeedbackPreservesProvokingVertex) {
builder.Warn("transformFeedbackPreservesProvokingVertex",
"unsupported; the captured vertex order for strips/fans is not guaranteed by the "
"spec even though the flat-shading convention is correct");
}
if (provokingVertexLast && transformFeedbackPreservesProvokingVertex &&
!transformFeedbackPreservesTriangleFanProvokingVertex && !provokingVertexModePerPipeline) {
builder.Warn("transformFeedbackPreservesTriangleFanProvokingVertex",
"unsupported and per-pipeline modes unavailable; the transform-feedback "
"provoking-vertex guarantee is left off so GL_TRIANGLE_FAN pipelines stay legal");
}
Bool primitiveTopologyListRestart = false;
if (vkGetPhysicalDeviceFeatures2Fn != nullptr &&
HasVkExtension(deviceExtensions, VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME)) {