From 92140405c1e7e98f44e9c25ceb89c0f1cb2f5477 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 31 Jul 2026 16:25:46 -0400 Subject: [PATCH] [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. --- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 81 +++++++++++++++++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 7 +- .../DirectVulkan/Renderer/VulkanRenderer.h | 4 + .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 9 --- .../MGToVk/RenderStateEnumConverter.cpp | 5 ++ 5 files changed, 95 insertions(+), 11 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index ac69853b..c3f3f7ad 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -1250,10 +1250,76 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->Clear(mask); } + // Vulkan has no LINE_LOOP topology; rewrite the draw as an indexed LINE_STRIP + // whose synthesized index list revisits the first vertex at the end. + static void DrawLineLoopAsIndexedStrip(const Vector& closedIndices, GLint basevertex) { + DrawIndexedCmd payload{}; + payload.mode = GL_LINE_STRIP; + payload.indexBufferView.indexType = GL_UNSIGNED_INT; + payload.indexBufferView.indexByteOffset = reinterpret_cast(closedIndices.data()); + payload.indexBufferView.indexByteSize = closedIndices.size() * sizeof(Uint32); + payload.indexBufferView.forceClientMemory = true; + payload.params.indexCount = static_cast(closedIndices.size()); + payload.params.instanceCount = 1; + payload.params.vertexOffset = basevertex; + pVulkanRenderer->DrawElements(payload); + } + + // Resolve a DrawElements index list (bound element-array buffer or client + // memory) into uint32 values with the loop-closing first index appended. + static Bool BuildClosedLineLoopIndices(GLsizei count, GLenum type, const void* indices, + Vector& outIndices) { + const SizeT indexSize = MG_Util::GetGLTypeSize(type); + if (indexSize == 0 || count < 2) { + return false; + } + const Uint8* indexBytes = nullptr; + const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); + const auto& indexBufferShared = vao.GetIndexBufferBindingSlot().GetBoundObject(); + if (indexBufferShared != nullptr) { + const SizeT offset = reinterpret_cast(indices); + const SizeT bufferSize = indexBufferShared->GetSize(); + if (indexBufferShared->MappedData() == nullptr || offset > bufferSize || + static_cast(count) * indexSize > bufferSize - offset) { + return false; + } + indexBufferShared->SyncPersistentMappedRange(); + indexBytes = indexBufferShared->MappedData() + offset; + } else { + indexBytes = static_cast(indices); + if (indexBytes == nullptr) { + return false; + } + } + outIndices.resize(static_cast(count) + 1); + for (GLsizei i = 0; i < count; ++i) { + switch (indexSize) { + case 1: outIndices[i] = indexBytes[i]; break; + case 2: outIndices[i] = reinterpret_cast(indexBytes)[i]; break; + default: outIndices[i] = reinterpret_cast(indexBytes)[i]; break; + } + } + outIndices[count] = outIndices[0]; + return true; + } + void DrawArrays(GLenum mode, GLint first, GLsizei count) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context"); + if (mode == GL_LINE_LOOP) { + if (count < 2) { + return; + } + Vector closedIndices(static_cast(count) + 1); + for (GLsizei i = 0; i < count; ++i) { + closedIndices[i] = static_cast(first + i); + } + closedIndices[count] = static_cast(first); + DrawLineLoopAsIndexedStrip(closedIndices, 0); + return; + } + DrawCmd payload{}; payload.mode = mode; payload.params.firstVertex = first; @@ -1266,6 +1332,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context"); + if (mode == GL_LINE_LOOP) { + Vector closedIndices; + if (BuildClosedLineLoopIndices(count, type, indices, closedIndices)) { + DrawLineLoopAsIndexedStrip(closedIndices, 0); + } + return; + } + DrawIndexedCmd payload{}; payload.mode = mode; payload.indexBufferView.indexType = type; @@ -1334,6 +1408,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElementsBaseVertex called with null GL context"); + if (mode == GL_LINE_LOOP) { + Vector closedIndices; + if (BuildClosedLineLoopIndices(count, type, indices, closedIndices)) { + DrawLineLoopAsIndexedStrip(closedIndices, basevertex); + } + return; + } DrawIndexedCmd payload{}; payload.mode = mode; payload.indexBufferView.indexType = type; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 93e0c876..d6d7b424 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -2848,7 +2848,9 @@ void main() { } const Uint8* indexBytes = nullptr; - const auto& indexBufferShared = vao.GetIndexBufferBindingSlot().GetBoundObject(); + const auto& indexBufferShared = indexView.forceClientMemory + ? SharedPtr{} + : vao.GetIndexBufferBindingSlot().GetBoundObject(); if (indexBufferShared != nullptr) { const SizeT bufferSize = indexBufferShared->GetSize(); if (indexBufferShared->MappedData() == nullptr || indexView.indexByteOffset > bufferSize || @@ -3262,7 +3264,8 @@ void main() { } } - const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); + const auto* indexBuffer = + pIndexBufferView->forceClientMemory ? nullptr : vao.GetIndexBufferBindingSlot().GetBoundObject().get(); if (indexBuffer == nullptr) { // No element-array buffer: the view's byte offset is a raw client pointer // (desktop drivers accept client-memory indices and the GL CTS relies on diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 14c8385a..e2ee4a42 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -76,6 +76,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLenum indexType = GL_UNSIGNED_SHORT; SizeT indexByteOffset = 0; SizeT indexByteSize = 0; + // Interpret indexByteOffset as a raw client pointer even when an element + // array buffer is bound (backend-synthesized index lists, e.g. the + // GL_LINE_LOOP -> LINE_STRIP rewrite). + Bool forceClientMemory = false; }; struct DrawIndexedCmd { diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 898c3253..d11a61a6 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -57,15 +57,6 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } - if (activeBackendObject->GetBackendType() == BackendType::DirectVulkan && mode == GL_LINE_LOOP) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", functionName, - "Primitive mode GL_LINE_LOOP is not supported by the DirectVulkan backend.")); - return false; - } - const auto& vao = MG_State::pGLContext->GetBoundVertexArray(); if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) { MG_State::pGLContext->RecordError( diff --git a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp index 61da4390..8a32b3ef 100644 --- a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp @@ -25,6 +25,11 @@ namespace MobileGL { case GL_TRIANGLE_FAN: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; case GL_LINE_LOOP: + // DrawArrays/DrawElements rewrite line loops into closed indexed + // strips; entry points without that rewrite (instanced/indirect) + // degrade to an open strip, which only misses the closing segment. + MGLOG_W("GL_LINE_LOOP without index rewrite; drawing as LINE_STRIP"); + return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; default: MGLOG_W("Unrecognized primitive topology"); return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;