From 48dd1c595694eaa040e1c5007f160fac3eb08c6d Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 31 Jul 2026 17:08:37 -0400 Subject: [PATCH] [Feat] (MG_State, MG_Impl): transform feedback state, validation and reflection First stage of GL 3.0 transform feedback: glTransformFeedbackVaryings / glGetTransformFeedbackVarying / glBeginTransformFeedback / glEndTransformFeedback were unimplemented stubs. This adds - per-program capture state: requested varyings apply on the next link and resolve against the last vertex-processing stage's linker objects (with gl_Position/gl_PointSize handled as builtins), failing the link on unknown or duplicate names or exceeded interleaved/separate limits, with offsets and strides computed per GL rules; - context Begin/End state with the GL 3.3 error semantics: invalid primitive modes, redundant Begin/End, missing program or capture-buffer bindings, primitive-mode compatibility at draw time, and the while-active prohibitions on rebinding capture buffers, switching programs, and relinking the captured program; - GetProgramiv TRANSFORM_FEEDBACK_* queries and a 4-slot bound on indexed GL_TRANSFORM_FEEDBACK_BUFFER binding points. KHR-GL33.transform_feedback api_errors/linking_errors/get_xfb_varying now pass; GPU-side capture is the remaining stage. --- MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp | 16 ++ MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp | 5 + .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 78 +++++++++ MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 2 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 8 +- .../MG_Impl/GLImpl/Program/GL_Program.cpp | 99 ++++++++++- MobileGL/MG_Impl/GLImpl/Program/GL_Program.h | 3 + MobileGL/MG_State/GLState/Core.h | 19 +++ .../GLState/ProgramState/ProgramObject.cpp | 160 ++++++++++++++++++ .../GLState/ProgramState/ProgramObject.h | 39 +++++ 10 files changed, 422 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp index b47b492e..4143ce81 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp @@ -1351,6 +1351,14 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return; if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, pointIndex)) return; + if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Transform feedback buffer bindings cannot change while transform " + "feedback is active.")); + return; + } MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, pointIndex); auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex); @@ -1384,6 +1392,14 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return; if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, index)) return; + if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Transform feedback buffer bindings cannot change while transform " + "feedback is active.")); + return; + } MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, index); auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index); diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp index d03542d3..9ee2f134 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp @@ -60,6 +60,11 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl { MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings; pointCount = std::min(pointCount, static_cast(std::max(backendCount, 0))); } + if (target == BufferTarget::TransformFeedback) { + // GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS bounds the indexed capture + // binding points in GL 3.3 (no ARB_transform_feedback3). + pointCount = std::min(pointCount, 4); + } if (index < pointCount) { return true; diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index d11a61a6..5bc4ee39 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -66,6 +66,34 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } + // While transform feedback is active the draw's primitive type must match + // the feedback primitive mode (GL 3.3 core 13.2.2). + if (MG_State::pGLContext->IsTransformFeedbackActive()) { + const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode(); + Bool compatible = false; + switch (feedbackMode) { + case GL_POINTS: + compatible = mode == GL_POINTS; + break; + case GL_LINES: + compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP; + break; + case GL_TRIANGLES: + compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN; + break; + default: + break; + } + if (!compatible) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", functionName, + "Primitive mode is incompatible with the active transform feedback primitive mode.")); + return false; + } + } + return true; } @@ -438,4 +466,54 @@ namespace MobileGL::MG_Impl::GLImpl { DrawElements_Backend(mode, count, type, indices); } + void BeginTransformFeedback(GLenum primitiveMode) { + if (primitiveMode != GL_POINTS && primitiveMode != GL_LINES && primitiveMode != GL_TRIANGLES) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "primitiveMode must be GL_POINTS, GL_LINES or GL_TRIANGLES.")); + return; + } + if (MG_State::pGLContext->IsTransformFeedbackActive()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is already active.")); + return; + } + const auto& program = MG_State::pGLContext->GetCurrentProgram(); + if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "No program with transform feedback varyings is active.")); + return; + } + // Every capture buffer slot the program's mode uses must have a buffer bound. + const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount(); + for (SizeT i = 0; i < usedBufferCount; ++i) { + const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, + static_cast(i)); + if (point.GetBoundObject() == nullptr) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "Transform feedback buffer binding point " + std::to_string(i) + " has no buffer bound.")); + return; + } + } + MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program); + } + + void EndTransformFeedback(void) { + if (!MG_State::pGLContext->IsTransformFeedbackActive()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not active.")); + return; + } + MG_State::pGLContext->EndTransformFeedback(); + } + } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index 122d668f..cc6c8e2e 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -11,6 +11,8 @@ namespace MobileGL::MG_Impl::GLImpl { /* @INSERTION_POINT:FUNCTION_DECLARATION@ */ + void BeginTransformFeedback(GLenum primitiveMode); + void EndTransformFeedback(void); void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); void MemoryBarrier(GLbitfield barriers); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 43e6b6fd..949d5711 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -236,12 +236,12 @@ DECLARE_GL_FUNCTION_HEAD(void, DeleteVertexArrays, GLsizei n, const GLuint* arra DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays) DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array) DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data) -DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode) -DECLARE_GL_FUNCTION_STUB_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndTransformFeedback) +DECLARE_GL_FUNCTION_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode) +DECLARE_GL_FUNCTION_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndTransformFeedback) DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size) DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer) -DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name) +DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode) +DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name) DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer) DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params) DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params) diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index 816cb2ef..1a4d1274 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -613,6 +613,18 @@ namespace MobileGL::MG_Impl::GLImpl { *params = programObject->GetActiveUniformBlocksMaxNameLength() + 1; MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); break; + case GL_TRANSFORM_FEEDBACK_VARYINGS: + *params = static_cast(programObject->GetTransformFeedbackVaryingCount()); + MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); + break; + case GL_TRANSFORM_FEEDBACK_BUFFER_MODE: + *params = static_cast(programObject->GetTransformFeedbackBufferMode()); + MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); + break; + case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH: + *params = programObject->GetTransformFeedbackVaryingMaxLength(); + MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); + break; case GL_COMPUTE_WORK_GROUP_SIZE: { // GL >= 4.3 if (!programObject->GetLinkStatus() || programObject->GetShaderIndexByStage(ShaderStage::Compute) < 0) { MG_State::pGLContext->RecordError( @@ -632,9 +644,6 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_PROGRAM_BINARY_LENGTH: - case GL_TRANSFORM_FEEDBACK_BUFFER_MODE: - case GL_TRANSFORM_FEEDBACK_VARYINGS: - case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH: case GL_GEOMETRY_VERTICES_OUT: case GL_GEOMETRY_INPUT_TYPE: case GL_GEOMETRY_OUTPUT_TYPE: @@ -857,6 +866,18 @@ namespace MobileGL::MG_Impl::GLImpl { if (!programObject) return; MGLOG_D("%s: linking program %d", __func__, program); + // Relinking the program an active transform feedback captures from would + // invalidate its varyings mid-capture (GL 3.3 core 2.11.3). + if (MG_State::pGLContext->IsTransformFeedbackActive() && + MG_State::pGLContext->GetTransformFeedbackProgram().get() == programObject.get()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "The program used by active transform feedback cannot be relinked.")); + return; + } + static Bool allowVSOnlyPrograms; static Bool initialized = false; if (!initialized) { @@ -900,6 +921,16 @@ namespace MobileGL::MG_Impl::GLImpl { void UseProgram_State(GLuint program) { MGLOG_D("UseProgram_State: program=%u", program); + // GL 3.3 core 2.11.3: the program in use may not change while transform + // feedback is active (there is no pause in 3.3). + if (MG_State::pGLContext->IsTransformFeedbackActive()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "The current program cannot change while transform feedback is active.")); + return; + } + if (program == 0) { MG_State::pGLContext->UseProgram(0); return; @@ -2225,4 +2256,66 @@ namespace MobileGL::MG_Impl::GLImpl { void ValidateProgram(GLuint program) { ValidateProgram_State(program); } + + void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) { + auto& programObject = TryToGetProgramObject(program); + if (!programObject) return; + if (bufferMode != GL_INTERLEAVED_ATTRIBS && bufferMode != GL_SEPARATE_ATTRIBS) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, "bufferMode is not a valid capture mode.")); + return; + } + if (count < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "count must be non-negative.")); + return; + } + // GL 3.3 core: SEPARATE_ATTRIBS count may not exceed the separate-attrib limit. + if (bufferMode == GL_SEPARATE_ATTRIBS && count > 4) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "count exceeds GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.")); + return; + } + Vector names; + names.reserve(static_cast(count)); + for (GLsizei i = 0; i < count; ++i) { + names.emplace_back(varyings != nullptr && varyings[i] != nullptr ? varyings[i] : ""); + } + programObject->SetTransformFeedbackVaryings(Move(names), bufferMode); + } + + void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, + GLenum* type, GLchar* name) { + auto& programObject = TryToGetProgramObject(program); + if (!programObject) return; + if (!programObject->GetLinkStatus()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + std::to_string(program) + " has not been successfully linked.")); + return; + } + const auto* varying = programObject->GetTransformFeedbackVarying(index); + if (varying == nullptr) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "index is not an active transform feedback varying of the program.")); + return; + } + if (size != nullptr) *size = varying->size; + if (type != nullptr) *type = varying->type; + GLsizei written = 0; + if (name != nullptr && bufSize > 0) { + written = std::min(bufSize - 1, static_cast(varying->name.size())); + Memcpy(name, varying->name.data(), static_cast(written)); + name[written] = '\0'; + } + if (length != nullptr) *length = written; + } } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h index 07e56ff6..ff5375d4 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h @@ -138,4 +138,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name); void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); void ValidateProgram(GLuint program); + void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode); + void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, + GLenum* type, GLchar* name); } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 01f1b5eb..283e1285 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -211,6 +211,22 @@ namespace MobileGL { void SetScissorBox(IntVec4 box); // x, y, width, height const IntVec4& GetScissorBox() const; // x, y, width, height + // Transform feedback (GL 3.0 core Begin/End; no feedback objects yet) + void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr& program) { + m_transformFeedbackActive = true; + m_transformFeedbackPrimitiveMode = primitiveMode; + m_transformFeedbackProgram = program; + } + void EndTransformFeedback() { + m_transformFeedbackActive = false; + m_transformFeedbackProgram.reset(); + } + Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; } + GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; } + const SharedPtr& GetTransformFeedbackProgram() const { + return m_transformFeedbackProgram; + } + // Framebuffer void GenFramebufferNames(Uint number, Vector& framebuffers); const SharedPtr& GetFramebufferObject(Uint index); @@ -243,6 +259,9 @@ namespace MobileGL { BufferState m_bufferState; VertexArrayState m_vertexArrayState; Array m_currentVertexAttributes{}; + Bool m_transformFeedbackActive = false; + GLenum m_transformFeedbackPrimitiveMode = GL_POINTS; + SharedPtr m_transformFeedbackProgram; 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 2de41eda..3b842cf7 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -170,9 +170,163 @@ namespace MobileGL::MG_State::GLState { m_uniformNameMaxLength = 0; m_attribInNameMaxLength = 0; m_uniformBlockNameMaxLength = 0; + m_xfbVaryings.clear(); + m_xfbStrides.clear(); + m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; + m_xfbVaryingNameMaxLength = 0; m_linkStatus = false; } + namespace { + // GL type enum for a vertex-stage output symbol captured by transform + // feedback. Covers the scalar/vector/matrix float+integer types transform + // feedback may legally capture in GL 3.3. + Bool ResolveXfbSymbolType(const glslang::TType& type, GLenum& outType, GLint& outArraySize, + Uint32& outBytesPerElement) { + outArraySize = type.isArray() ? type.getOuterArraySize() : 1; + const Int columns = type.isMatrix() ? type.getMatrixCols() : 1; + const Int components = type.isMatrix() ? type.getMatrixRows() + : (type.isVector() ? type.getVectorSize() : 1); + const glslang::TBasicType basic = type.getBasicType(); + static constexpr GLenum kFloatTypes[5] = {0, GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4}; + static constexpr GLenum kIntTypes[5] = {0, GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4}; + static constexpr GLenum kUintTypes[5] = {0, GL_UNSIGNED_INT, GL_UNSIGNED_INT_VEC2, GL_UNSIGNED_INT_VEC3, + GL_UNSIGNED_INT_VEC4}; + if (type.isMatrix()) { + if (basic != glslang::EbtFloat) return false; + static constexpr GLenum kMatTypes[5][5] = { + {}, {}, + {0, 0, GL_FLOAT_MAT2, GL_FLOAT_MAT2x3, GL_FLOAT_MAT2x4}, + {0, 0, GL_FLOAT_MAT3x2, GL_FLOAT_MAT3, GL_FLOAT_MAT3x4}, + {0, 0, GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3, GL_FLOAT_MAT4}, + }; + if (columns < 2 || columns > 4 || components < 2 || components > 4) return false; + outType = kMatTypes[columns][components]; + } else if (components >= 1 && components <= 4) { + switch (basic) { + case glslang::EbtFloat: outType = kFloatTypes[components]; break; + case glslang::EbtInt: outType = kIntTypes[components]; break; + case glslang::EbtUint: outType = kUintTypes[components]; break; + default: return false; + } + } else { + return false; + } + outBytesPerElement = static_cast(columns * components) * 4u; + return true; + } + } // namespace + + Bool ProgramObject::ResolveTransformFeedbackVaryings() { + m_xfbVaryings.clear(); + m_xfbStrides.clear(); + m_xfbBufferMode = m_requestedXfbBufferMode; + m_xfbVaryingNameMaxLength = 0; + if (m_requestedXfbVaryings.empty()) { + return true; + } + + // Capture happens at the last vertex-processing stage (geometry, then + // tessellation evaluation, then vertex). + const glslang::TIntermediate* captureIntermediate = nullptr; + for (EShLanguage stage : {EShLangGeometry, EShLangTessEvaluation, EShLangVertex}) { + captureIntermediate = m_program->getIntermediate(stage); + if (captureIntermediate != nullptr) { + break; + } + } + if (captureIntermediate == nullptr) { + m_infoLog = "Transform feedback varyings requested but the program has no vertex-processing stage."; + return false; + } + const glslang::TIntermAggregate* linkerObjects = captureIntermediate->findLinkerObjects(); + + const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS; + Uint32 interleavedOffset = 0; + for (SizeT i = 0; i < m_requestedXfbVaryings.size(); ++i) { + const String& name = m_requestedXfbVaryings[i]; + for (SizeT j = 0; j < i; ++j) { + if (m_requestedXfbVaryings[j] == name) { + m_infoLog = "Transform feedback varying '" + name + "' is specified more than once."; + return false; + } + } + + XfbVarying varying; + varying.name = name; + Uint32 bytesPerElement = 0; + Bool resolved = false; + if (name == "gl_Position") { + varying.type = GL_FLOAT_VEC4; + varying.size = 1; + bytesPerElement = 16; + resolved = true; + } else if (name == "gl_PointSize") { + varying.type = GL_FLOAT; + varying.size = 1; + bytesPerElement = 4; + resolved = true; + } else if (linkerObjects != nullptr) { + for (const auto* node : linkerObjects->getSequence()) { + const glslang::TIntermSymbol* symbol = node->getAsSymbolNode(); + if (symbol == nullptr || symbol->getType().getQualifier().storage != glslang::EvqVaryingOut) { + continue; + } + if (symbol->getName() != name.c_str()) { + continue; + } + resolved = ResolveXfbSymbolType(symbol->getType(), varying.type, varying.size, bytesPerElement); + break; + } + } + if (!resolved) { + m_infoLog = "Transform feedback varying '" + name + "' is not an output of the vertex stage."; + return false; + } + + varying.byteSize = bytesPerElement * static_cast(varying.size); + if (interleaved) { + varying.bufferIndex = 0; + varying.offsetBytes = interleavedOffset; + interleavedOffset += varying.byteSize; + } else { + varying.bufferIndex = static_cast(i); + varying.offsetBytes = 0; + } + m_xfbVaryingNameMaxLength = + std::max(m_xfbVaryingNameMaxLength, static_cast(name.size()) + 1); + m_xfbVaryings.push_back(Move(varying)); + } + + constexpr Uint32 kMaxSeparateAttribs = 4; + constexpr Uint32 kMaxSeparateComponents = 4; + constexpr Uint32 kMaxInterleavedComponents = 64; + if (interleaved) { + if (interleavedOffset > kMaxInterleavedComponents * 4) { + m_infoLog = "Transform feedback interleaved capture exceeds " + "GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS."; + return false; + } + m_xfbStrides.assign(1, interleavedOffset); + } else { + if (m_xfbVaryings.size() > kMaxSeparateAttribs) { + m_infoLog = "Transform feedback separate capture exceeds " + "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS."; + return false; + } + m_xfbStrides.resize(m_xfbVaryings.size()); + for (SizeT i = 0; i < m_xfbVaryings.size(); ++i) { + if (m_xfbVaryings[i].byteSize > kMaxSeparateComponents * 4) { + m_infoLog = "Transform feedback varying '" + m_xfbVaryings[i].name + + "' exceeds GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS."; + return false; + } + m_xfbStrides[i] = m_xfbVaryings[i].byteSize; + } + } + return 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(), @@ -327,6 +481,12 @@ namespace MobileGL::MG_State::GLState { if (!ValidateFragmentOutputLocations()) { return; } + if (!ResolveTransformFeedbackVaryings()) { + m_linkStatus = false; + MGLOG_E("ProgramObject %u: transform feedback varying resolution failed: %s", m_externalIndex, + m_infoLog.c_str()); + return; + } MGLOG_D("ProgramObject %u: Starting binary generation", m_externalIndex); GenerateBinary(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 6a3cc360..1d98fdfc 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -465,6 +465,33 @@ namespace MobileGL::MG_State::GLState { return it == m_shaders.end() ? -1 : (Int)std::distance(m_shaders.begin(), it); } + // Transform feedback (GL 3.0 core: glTransformFeedbackVaryings applies on + // the NEXT link; the linked snapshot below is what draws and queries see). + struct XfbVarying { + String name; + GLenum type = GL_FLOAT; + GLint size = 1; // array element count + Uint32 bufferIndex = 0; // capture buffer slot + Uint32 offsetBytes = 0; // offset within the capture buffer + Uint32 byteSize = 0; // bytes captured per vertex for this varying + }; + void SetTransformFeedbackVaryings(Vector&& names, GLenum bufferMode) { + m_requestedXfbVaryings = Move(names); + m_requestedXfbBufferMode = bufferMode; + } + GLenum GetTransformFeedbackBufferMode() const { return m_xfbBufferMode; } + SizeT GetTransformFeedbackVaryingCount() const { return m_xfbVaryings.size(); } + const XfbVarying* GetTransformFeedbackVarying(SizeT index) const { + return index < m_xfbVaryings.size() ? &m_xfbVaryings[index] : nullptr; + } + const Vector& GetTransformFeedbackVaryings() const { return m_xfbVaryings; } + // Stride of one captured vertex in the given capture buffer slot. + Uint32 GetTransformFeedbackStride(Uint32 bufferIndex) const { + return bufferIndex < m_xfbStrides.size() ? m_xfbStrides[bufferIndex] : 0; + } + SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); } + Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; } + Uint GetExternalIndex() const { return m_externalIndex; } // Globally-unique, never-reused id for this program object's lifetime. Unlike the GL // name (external index), which is freed to a LIFO list and immediately handed back by @@ -475,6 +502,10 @@ namespace MobileGL::MG_State::GLState { private: void ResetLinkArtifacts(); void DoReflection(); + // Resolves the requested transform feedback varyings against the linked + // vertex stage; fails the link (GL semantics) on unknown or duplicate + // names or exceeded capture limits. + Bool ResolveTransformFeedbackVaryings(); void GenerateBinary(); void WaitUntilGenerationCompleted() const; void AddDefaultFragmentShaderIfMissing(); @@ -558,5 +589,13 @@ namespace MobileGL::MG_State::GLState { mutable Uint32 m_backendHashMemoVersion = ~0u; Uint32 m_uboContentVersion = 0; Uint32 m_linkVersion = 0; + + // Transform feedback: request (applies at next link) and linked snapshot. + Vector m_requestedXfbVaryings; + GLenum m_requestedXfbBufferMode = GL_INTERLEAVED_ATTRIBS; + Vector m_xfbVaryings; + Vector m_xfbStrides; + GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; + Int m_xfbVaryingNameMaxLength = 0; }; } // namespace MobileGL::MG_State::GLState