From cb2ba71feb31047b6c511ad0b114053b6416c6c6 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Tue, 4 Aug 2026 20:03:24 -0400 Subject: [PATCH] [Feat] (DirectVulkan): run the tessellation stages The backend already turned a tessellation control/evaluation shader into the right VkShaderStage, but nothing downstream knew what to do with it: GL_PATCHES had no topology, so it fell through to the triangle-list default, and the pipeline carried no tessellation state at all. A GL_PATCHES draw therefore ran the vertex and fragment stages over raw triangles. Map GL_PATCHES to VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, carry GL_PATCH_VERTICES into the pipeline as patchControlPoints (part of the key, since two patch sizes are two pipelines), attach VkPipelineTessellationStateCreateInfo for a patch topology only, and enable the tessellationShader device feature. POST reports the feature, because without it a program with a tessellation stage cannot build a pipeline at all and GL_PATCHES draws render nothing. --- .../DirectVulkan/Renderer/PipelineFactory.cpp | 8 ++++++++ .../DirectVulkan/Renderer/PipelineFactory.h | 2 ++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 2 ++ .../Converters/MGToVk/RenderStateEnumConverter.cpp | 4 ++++ MobileGL/MG_Util/SelfTest/DriverPost.cpp | 12 ++++++++++++ 5 files changed, 28 insertions(+) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp index e11183f8..b7e8e70c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp @@ -205,6 +205,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology))); XXHASH_VERIFY( XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable))); + XXHASH_VERIFY(XXH64_update(m_hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace))); @@ -380,6 +381,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { ia.topology = payload.topology; ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE; + // Only a patch topology has a tessellation stage to configure; leaving the pointer null + // otherwise is what the spec expects. + VkPipelineTessellationStateCreateInfo tessellation{VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO}; + tessellation.patchControlPoints = payload.patchControlPoints; + VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; vpci.viewportCount = 1; vpci.scissorCount = 1; @@ -444,6 +450,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { gpi.pStages = payload.stages->data(); gpi.pVertexInputState = payload.vertexInputState; gpi.pInputAssemblyState = &ia; + gpi.pTessellationState = + payload.topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST ? &tessellation : nullptr; gpi.pViewportState = &vpci; gpi.pRasterizationState = &raster; gpi.pMultisampleState = &ms; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h index 292c98fa..2987534f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h @@ -30,6 +30,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 subpass = 0; VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; Bool primitiveRestartEnable = false; + // GL_PATCH_VERTICES; only read for a PATCH_LIST topology. + Uint32 patchControlPoints = 3; VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL; VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT; VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 98a8036e..093ce43d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -4209,6 +4209,7 @@ void main() { .subpass = 0, .topology = vkTopology, .primitiveRestartEnable = primitiveRestartEnabled, + .patchControlPoints = static_cast(MG_State::pGLContext->GetPatchVertices()), .polygonMode = effectivePolygonMode, .cullMode = cullFaceEnabled ? MG_Util::ConvertCullFaceModeToVkEnum(MG_State::pGLContext->GetCullFaceMode(), invertClockwise) @@ -9794,6 +9795,7 @@ void main() { ? VK_FALSE : supportedDeviceFeatures.robustBufferAccess; deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader; + deviceFeatures.tessellationShader = supportedDeviceFeatures.tessellationShader; deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend; m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE; deviceFeatures.fillModeNonSolid = supportedDeviceFeatures.fillModeNonSolid; diff --git a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp index 1d9e1bbd..2493606e 100644 --- a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp @@ -38,6 +38,10 @@ namespace MobileGL { return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY; case GL_TRIANGLE_STRIP_ADJACENCY: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY; + case GL_PATCHES: + // The tessellator decides what a patch becomes; its vertex count is pipeline + // state (patchControlPoints), not part of the topology. + return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; default: MGLOG_W("Unrecognized primitive topology"); return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index f9871f57..4a5d2fb7 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -1431,6 +1431,18 @@ namespace MobileGL::MG_Util::SelfTest { "hard-fails at draw"); } + // Core 1.0 features the backend turns GL stages into pipeline stages with. + VkPhysicalDeviceFeatures coreFeatures{}; + vkGetPhysicalDeviceFeatures(physicalDevice, &coreFeatures); + if (coreFeatures.tessellationShader == VK_TRUE) { + builder.Pass("tessellationShader", + "supported (GL_PATCHES draws run the tessellation control/evaluation stages)"); + } else { + builder.Warn("tessellationShader", + "unsupported; a program with a tessellation control/evaluation shader cannot build a " + "pipeline, so GL_PATCHES draws render nothing"); + } + Bool vertexAttributeInstanceRateDivisor = false; if (vkGetPhysicalDeviceFeatures2Fn != nullptr && HasVkExtension(deviceExtensions, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {