From 641bc0cdd985ca540f94b5be69bddc723bd81ca4 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 31 Jul 2026 17:40:42 -0400 Subject: [PATCH] [Feat] (MG_Impl): transform feedback primitive queries glBeginQuery/glEndQuery now accept GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN and GL_PRIMITIVES_GENERATED. The result comes from CPU-side accounting: every captured draw adds the primitives it assembles, clamped by the capture buffers' remaining capacity in whole primitives (a full buffer stops recording, which is exactly what PRIMITIVES_WRITTEN reports), with the captured-vertex cursor resetting on glBeginTransformFeedback. Draws without a geometry stage write exactly what they assemble, so this is precise for them (KHR-GL33.transform_feedback.query_vertex_* now pass); geometry amplification is not modelled yet and the query_geometry_* variants still fail. --- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 69 +++++++++++++++++++ MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp | 63 +++++++++++++---- MobileGL/MG_State/GLState/Core.h | 16 +++++ 3 files changed, 133 insertions(+), 15 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 13edd611..bb385d8c 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -48,6 +48,72 @@ namespace MobileGL::MG_Impl::GLImpl { return true; } + // Primitives a draw of `count` vertices in `mode` assembles (0 for + // incomplete primitives). Used for the CPU-side transform feedback + // primitive accounting. + static Uint64 CountPrimitivesForDraw(GLenum mode, GLsizei count) { + if (count <= 0) return 0; + switch (mode) { + case GL_POINTS: return static_cast(count); + case GL_LINES: return static_cast(count / 2); + case GL_LINE_STRIP: return count >= 2 ? static_cast(count - 1) : 0; + case GL_LINE_LOOP: return count >= 2 ? static_cast(count) : 0; + case GL_TRIANGLES: return static_cast(count / 3); + case GL_TRIANGLE_STRIP: + case GL_TRIANGLE_FAN: return count >= 3 ? static_cast(count - 2) : 0; + default: return 0; + } + } + + // Accumulate the transform feedback primitive counter for a captured draw. + // Draws without a geometry stage write exactly the primitives they assemble, + // clamped by the capture buffers' remaining capacity (a full buffer stops + // recording whole primitives, which is what PRIMITIVES_WRITTEN reports). + // Geometry amplification is not modelled here. + static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) { + if (!MG_State::pGLContext->IsTransformFeedbackActive()) return; + Uint64 primitives = CountPrimitivesForDraw(mode, count); + if (primitives == 0) return; + + Uint64 verticesPerPrimitive = 1; + switch (mode) { + case GL_LINES: + case GL_LINE_STRIP: + case GL_LINE_LOOP: + verticesPerPrimitive = 2; + break; + case GL_TRIANGLES: + case GL_TRIANGLE_STRIP: + case GL_TRIANGLE_FAN: + verticesPerPrimitive = 3; + break; + default: + break; + } + + const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram(); + if (program != nullptr) { + // Capacity in captured vertices = the tightest bound buffer. + Uint64 capacityVertices = ~0ull; + for (SizeT i = 0; i < program->GetTransformFeedbackBufferCount(); ++i) { + const Uint32 stride = program->GetTransformFeedbackStride(static_cast(i)); + if (stride == 0) continue; + const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, + static_cast(i)); + const Range1D range = point.GetRange(); + const Uint64 bytes = range.end > range.start ? static_cast(range.end - range.start) : 0; + capacityVertices = std::min(capacityVertices, bytes / stride); + } + if (capacityVertices != ~0ull) { + const Uint64 usedVertices = MG_State::pGLContext->GetTransformFeedbackCapturedVertices(); + const Uint64 remainingVertices = capacityVertices > usedVertices ? capacityVertices - usedVertices : 0; + primitives = std::min(primitives, remainingVertices / verticesPerPrimitive); + } + } + MG_State::pGLContext->AddTransformFeedbackPrimitives(primitives); + MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive); + } + static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) { const auto& activeBackendObject = MG_Backend::pActiveBackendObject; if (!activeBackendObject) { @@ -425,12 +491,14 @@ namespace MobileGL::MG_Impl::GLImpl { void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; + AccountTransformFeedbackPrimitives(mode, count); DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex); } void DrawArrays(GLenum mode, GLint first, GLsizei count) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; + AccountTransformFeedbackPrimitives(mode, count); DrawArrays_Backend(mode, first, count); } @@ -467,6 +535,7 @@ namespace MobileGL::MG_Impl::GLImpl { void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; + AccountTransformFeedbackPrimitives(mode, count); DrawElements_Backend(mode, count, type, indices); } diff --git a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp index 64cb18a5..96d1af5c 100644 --- a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp +++ b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp @@ -27,6 +27,8 @@ namespace MobileGL::MG_Impl::GLImpl { Bool ended = false; Bool resultCached = false; Uint64 cachedResult = 0; + // Transform feedback primitive counter at BeginQuery time. + Uint64 counterSnapshot = 0; }; // Query calls may arrive from any thread (launchers migrate the context @@ -41,6 +43,9 @@ namespace MobileGL::MG_Impl::GLImpl { GLuint g_nextQueryId = 1; // Id of the query currently active on GL_TIME_ELAPSED (0 = none). GLuint g_activeTimeElapsedQueryId = 0; + // Ids of the queries active on the transform feedback targets (0 = none). + GLuint g_activePrimitivesWrittenQueryId = 0; + GLuint g_activePrimitivesGeneratedQueryId = 0; Bool TimerQueryDisabled() { return MG_Config::Features.DisableTimerQuery; @@ -204,10 +209,12 @@ namespace MobileGL::MG_Impl::GLImpl { } void BeginQuery(GLenum target, GLuint id) { - if (target != GL_TIME_ELAPSED) { - // Only GL_TIME_ELAPSED timer queries are implemented (occlusion and - // primitive queries remain stubs); GL_TIMESTAMP is not a valid - // BeginQuery target either. + const Bool isTransformFeedbackQuery = + target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED; + if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery) { + // GL_TIME_ELAPSED timer queries and the transform feedback primitive + // queries are implemented (occlusion queries remain stubs); + // GL_TIMESTAMP is not a valid BeginQuery target either. RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported."); return; } @@ -221,9 +228,13 @@ namespace MobileGL::MG_Impl::GLImpl { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist."); return; } - if (g_activeTimeElapsedQueryId != 0) { + GLuint& activeQueryId = isTransformFeedbackQuery + ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId + : g_activePrimitivesGeneratedQueryId) + : g_activeTimeElapsedQueryId; + if (activeQueryId != 0) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, - "A query is already active on GL_TIME_ELAPSED."); + "A query is already active on this target."); return; } if (queryObject->active) { @@ -239,25 +250,47 @@ namespace MobileGL::MG_Impl::GLImpl { ResetQueryObjectLocked(queryObject); // discard any previous result queryObject->target = target; queryObject->active = true; - const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery; - queryObject->backendHandle = - (!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr; - g_activeTimeElapsedQueryId = id; + if (isTransformFeedbackQuery) { + // CPU accounting: captured draws bump the context counter; the query + // result is the delta between Begin and End. Without geometry-stage + // amplification the assembled count IS the written/generated count. + queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter(); + } else { + const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery; + queryObject->backendHandle = + (!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr; + } + activeQueryId = id; } void EndQuery(GLenum target) { - if (target != GL_TIME_ELAPSED) { + const Bool isTransformFeedbackQuery = + target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED; + if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery) { RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported."); return; } const std::lock_guard lock(g_queryObjectsMutex); - if (g_activeTimeElapsedQueryId == 0) { - RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on GL_TIME_ELAPSED."); + GLuint& activeQueryId = isTransformFeedbackQuery + ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId + : g_activePrimitivesGeneratedQueryId) + : g_activeTimeElapsedQueryId; + if (activeQueryId == 0) { + RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target."); return; } - auto* queryObject = FindQueryObjectLocked(g_activeTimeElapsedQueryId); + auto* queryObject = FindQueryObjectLocked(activeQueryId); if (!queryObject) { - g_activeTimeElapsedQueryId = 0; // should not happen; keep state consistent + activeQueryId = 0; // should not happen; keep state consistent + return; + } + if (isTransformFeedbackQuery) { + queryObject->cachedResult = + MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter() - queryObject->counterSnapshot; + queryObject->resultCached = true; + queryObject->active = false; + queryObject->ended = true; + activeQueryId = 0; return; } EndTimeElapsedQueryLocked(queryObject); diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 33e78c35..108f6e8a 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -217,6 +217,7 @@ namespace MobileGL { m_transformFeedbackPrimitiveMode = primitiveMode; m_transformFeedbackProgram = program; ++m_transformFeedbackGeneration; + m_transformFeedbackCapturedVertices = 0; } void EndTransformFeedback() { m_transformFeedbackActive = false; @@ -230,6 +231,19 @@ namespace MobileGL { // Bumped on every BeginTransformFeedback; the backend uses it to // distinguish "resume appending" from "fresh capture". Uint64 GetTransformFeedbackGeneration() const { return m_transformFeedbackGeneration; } + // CPU-side primitive accounting for the transform feedback queries: + // every captured draw adds its primitive count (draws without a + // geometry stage write exactly what they generate). + void AddTransformFeedbackPrimitives(Uint64 primitives) { + m_transformFeedbackPrimitiveCounter += primitives; + } + Uint64 GetTransformFeedbackPrimitiveCounter() const { return m_transformFeedbackPrimitiveCounter; } + // Vertices already captured since BeginTransformFeedback (drives the + // buffer-capacity clamp on the primitives-written accounting). + void AddTransformFeedbackCapturedVertices(Uint64 vertices) { + m_transformFeedbackCapturedVertices += vertices; + } + Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; } // Framebuffer void GenFramebufferNames(Uint number, Vector& framebuffers); @@ -267,6 +281,8 @@ namespace MobileGL { GLenum m_transformFeedbackPrimitiveMode = GL_POINTS; SharedPtr m_transformFeedbackProgram; Uint64 m_transformFeedbackGeneration = 0; + Uint64 m_transformFeedbackPrimitiveCounter = 0; + Uint64 m_transformFeedbackCapturedVertices = 0; TextureState m_textureState; ProgramState m_programState; RenderState m_renderState;