diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index bb385d8c..3c57b71f 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -74,6 +74,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!MG_State::pGLContext->IsTransformFeedbackActive()) return; Uint64 primitives = CountPrimitivesForDraw(mode, count); if (primitives == 0) return; + MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives); Uint64 verticesPerPrimitive = 1; switch (mode) { @@ -579,6 +580,66 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program); } + // Vulkan transform feedback captures triangle strips in plain (i, i+1, i+2) + // vertex order, but GL decomposes odd strip triangles as (i+1, i, i+2) + // (GL 4.6 table 10.1). With the geometry stage's statically-known strip + // lengths the captured records are reordered in place: swap the first two + // vertex records of every odd triangle within each emitted strip. + static void FixupGsStripCaptureOrder(const SharedPtr& program, + Uint64 inputPrimitives) { + if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) { + return; + } + const auto& stripTriangles = program->GetGsStripTriangles(); + + // Global triangle indices whose leading vertex pair must swap. + Vector swapTriangles; + Uint64 triangleBase = 0; + for (Uint64 input = 0; input < inputPrimitives; ++input) { + for (const Uint32 stripLength : stripTriangles) { + for (Uint32 t = 1; t < stripLength; t += 2) { + swapTriangles.push_back(triangleBase + t); + } + triangleBase += stripLength; + } + } + if (swapTriangles.empty()) { + return; + } + + for (SizeT bufferIndex = 0; bufferIndex < program->GetTransformFeedbackBufferCount(); ++bufferIndex) { + const Uint32 stride = program->GetTransformFeedbackStride(static_cast(bufferIndex)); + if (stride == 0) continue; + const auto& bindingPoint = + MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, + static_cast(bufferIndex)); + const auto& buffer = bindingPoint.GetBoundObject(); + if (buffer == nullptr) continue; + const Range1D range = bindingPoint.GetRange(); + const Uint8* mapped = buffer->MappedData(); + if (mapped == nullptr) continue; + // The geometry stage amplifies, so the CPU vertex counter does not bound + // the capture; the binding range's whole-triangle capacity does. + const Uint64 rangeBytes = range.end > range.start ? static_cast(range.end - range.start) : 0; + const Uint64 capturedTriangles = std::min(triangleBase, (rangeBytes / stride) / 3); + + // Observed Vulkan capture order for odd strip triangles is (i, i+2, i+1) + // (winding preserved by swapping the trailing pair); GL wants + // (i+1, i, i+2), which is one rotation away: (a,b,c) -> (c,a,b). + Vector scratch(stride); + for (const Uint64 triangle : swapTriangles) { + if (triangle >= capturedTriangles) break; + const SizeT v0Offset = static_cast(range.start) + static_cast(triangle * 3) * stride; + const SizeT v1Offset = v0Offset + stride; + const SizeT v2Offset = v1Offset + stride; + Memcpy(scratch.data(), mapped + v2Offset, stride); + buffer->WritebackFromBackend({const_cast(mapped) + v1Offset, stride}, v2Offset); + buffer->WritebackFromBackend({const_cast(mapped) + v0Offset, stride}, v1Offset); + buffer->WritebackFromBackend({scratch.data(), stride}, v0Offset); + } + } + } + void EndTransformFeedback(void) { if (!MG_State::pGLContext->IsTransformFeedbackActive()) { MG_State::pGLContext->RecordError( @@ -586,6 +647,8 @@ namespace MobileGL::MG_Impl::GLImpl { MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not active.")); return; } + const auto capturedProgram = MG_State::pGLContext->GetTransformFeedbackProgram(); + const Uint64 inputPrimitives = MG_State::pGLContext->GetTransformFeedbackInputPrimitives(); MG_State::pGLContext->EndTransformFeedback(); // Captured results must be visible to MapBuffer/GetBufferSubData after // End; the capture targets are host-coherent GPU memory, so completing @@ -599,6 +662,7 @@ namespace MobileGL::MG_Impl::GLImpl { } } } + FixupGsStripCaptureOrder(capturedProgram, inputPrimitives); } } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 108f6e8a..b4df3edb 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -218,6 +218,7 @@ namespace MobileGL { m_transformFeedbackProgram = program; ++m_transformFeedbackGeneration; m_transformFeedbackCapturedVertices = 0; + m_transformFeedbackInputPrimitives = 0; } void EndTransformFeedback() { m_transformFeedbackActive = false; @@ -244,6 +245,12 @@ namespace MobileGL { m_transformFeedbackCapturedVertices += vertices; } Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; } + // Raw assembled input primitives fed to the capture stage since Begin + // (pre-clamp; drives the GS strip capture-order fixup at EndTF). + void AddTransformFeedbackInputPrimitives(Uint64 primitives) { + m_transformFeedbackInputPrimitives += primitives; + } + Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; } // Framebuffer void GenFramebufferNames(Uint number, Vector& framebuffers); @@ -283,6 +290,7 @@ namespace MobileGL { Uint64 m_transformFeedbackGeneration = 0; Uint64 m_transformFeedbackPrimitiveCounter = 0; Uint64 m_transformFeedbackCapturedVertices = 0; + Uint64 m_transformFeedbackInputPrimitives = 0; TextureState m_textureState; ProgramState m_programState; RenderState m_renderState; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 3b842cf7..222c8f41 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -324,9 +324,76 @@ namespace MobileGL::MG_State::GLState { m_xfbStrides[i] = m_xfbVaryings[i].byteSize; } } + + ResolveGsTriangleStripCapture(captureIntermediate); return true; } + namespace { + // Extracts a geometry shader's per-invocation EmitVertex/EndPrimitive sequence + // when it is statically knowable (no emit inside selection/loop/switch). Vulkan + // transform feedback captures triangle strips in plain (i, i+1, i+2) order while + // GL decomposes odd strip triangles as (i+1, i, i+2) (GL 4.6 table 10.1); with + // the static strip lengths the capture buffer can be reordered after EndTF. + class GsEmitSequenceTraverser final : public glslang::TIntermTraverser { + public: + bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) override { + if (node->getOp() == glslang::EOpEmitVertex) { + ++emitCount; + hasEmit = true; + } else if (node->getOp() == glslang::EOpEndPrimitive) { + FlushStrip(); + } + return true; + } + bool visitSelection(glslang::TVisit, glslang::TIntermSelection*) override { + inControlFlow = true; + return true; + } + bool visitLoop(glslang::TVisit, glslang::TIntermLoop*) override { + inControlFlow = true; + return true; + } + bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch*) override { + inControlFlow = true; + return true; + } + void FlushStrip() { + if (emitCount >= 3) { + stripTriangles.push_back(static_cast(emitCount - 2)); + } + emitCount = 0; + } + + Vector stripTriangles; + Uint32 emitCount = 0; + Bool hasEmit = false; + Bool inControlFlow = false; + }; + } // namespace + + void ProgramObject::ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate) { + m_gsStripTriangles.clear(); + m_gsStripCaptureFixup = false; + if (captureIntermediate == nullptr || m_program == nullptr) { + return; + } + if (m_program->getIntermediate(EShLangGeometry) != captureIntermediate) { + return; + } + if (captureIntermediate->getOutputPrimitive() != glslang::ElgTriangleStrip) { + return; + } + GsEmitSequenceTraverser traverser; + const_cast(captureIntermediate)->getTreeRoot()->traverse(&traverser); + traverser.FlushStrip(); // the invocation end acts as an implicit EndPrimitive + if (!traverser.hasEmit || traverser.inControlFlow || traverser.stripTriangles.empty()) { + return; + } + m_gsStripTriangles = Move(traverser.stripTriangles); + m_gsStripCaptureFixup = true; + } + bool ProgramObject::ShaderIsAttached(const SharedPtr& shader) { MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get()); auto it = std::find_if(m_shaders.begin(), m_shaders.end(), diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 1d98fdfc..e9518edc 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -491,6 +491,12 @@ namespace MobileGL::MG_State::GLState { } SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); } Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; } + // True when the capture stage is a triangle-strip geometry shader with a + // statically-known emit sequence: the Vulkan capture order then needs the GL + // odd-triangle vertex swap after EndTransformFeedback. + Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; } + // Triangles per strip, in emission order, for ONE geometry invocation. + const Vector& GetGsStripTriangles() const { return m_gsStripTriangles; } Uint GetExternalIndex() const { return m_externalIndex; } // Globally-unique, never-reused id for this program object's lifetime. Unlike the GL @@ -506,6 +512,7 @@ namespace MobileGL::MG_State::GLState { // vertex stage; fails the link (GL semantics) on unknown or duplicate // names or exceeded capture limits. Bool ResolveTransformFeedbackVaryings(); + void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate); void GenerateBinary(); void WaitUntilGenerationCompleted() const; void AddDefaultFragmentShaderIfMissing(); @@ -595,6 +602,8 @@ namespace MobileGL::MG_State::GLState { GLenum m_requestedXfbBufferMode = GL_INTERLEAVED_ATTRIBS; Vector m_xfbVaryings; Vector m_xfbStrides; + Vector m_gsStripTriangles; + Bool m_gsStripCaptureFixup = false; GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; Int m_xfbVaryingNameMaxLength = 0; };