mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[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.
This commit is contained in:
@@ -48,6 +48,72 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
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<Uint64>(count);
|
||||||
|
case GL_LINES: return static_cast<Uint64>(count / 2);
|
||||||
|
case GL_LINE_STRIP: return count >= 2 ? static_cast<Uint64>(count - 1) : 0;
|
||||||
|
case GL_LINE_LOOP: return count >= 2 ? static_cast<Uint64>(count) : 0;
|
||||||
|
case GL_TRIANGLES: return static_cast<Uint64>(count / 3);
|
||||||
|
case GL_TRIANGLE_STRIP:
|
||||||
|
case GL_TRIANGLE_FAN: return count >= 3 ? static_cast<Uint64>(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<Uint32>(i));
|
||||||
|
if (stride == 0) continue;
|
||||||
|
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||||
|
static_cast<Uint>(i));
|
||||||
|
const Range1D range = point.GetRange();
|
||||||
|
const Uint64 bytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
||||||
|
capacityVertices = std::min<Uint64>(capacityVertices, bytes / stride);
|
||||||
|
}
|
||||||
|
if (capacityVertices != ~0ull) {
|
||||||
|
const Uint64 usedVertices = MG_State::pGLContext->GetTransformFeedbackCapturedVertices();
|
||||||
|
const Uint64 remainingVertices = capacityVertices > usedVertices ? capacityVertices - usedVertices : 0;
|
||||||
|
primitives = std::min<Uint64>(primitives, remainingVertices / verticesPerPrimitive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->AddTransformFeedbackPrimitives(primitives);
|
||||||
|
MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive);
|
||||||
|
}
|
||||||
|
|
||||||
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
||||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||||
if (!activeBackendObject) {
|
if (!activeBackendObject) {
|
||||||
@@ -425,12 +491,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
DrawArrays_Backend(mode, first, 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) {
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
DrawElements_Backend(mode, count, type, indices);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
Bool ended = false;
|
Bool ended = false;
|
||||||
Bool resultCached = false;
|
Bool resultCached = false;
|
||||||
Uint64 cachedResult = 0;
|
Uint64 cachedResult = 0;
|
||||||
|
// Transform feedback primitive counter at BeginQuery time.
|
||||||
|
Uint64 counterSnapshot = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Query calls may arrive from any thread (launchers migrate the context
|
// Query calls may arrive from any thread (launchers migrate the context
|
||||||
@@ -41,6 +43,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLuint g_nextQueryId = 1;
|
GLuint g_nextQueryId = 1;
|
||||||
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
||||||
GLuint g_activeTimeElapsedQueryId = 0;
|
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() {
|
Bool TimerQueryDisabled() {
|
||||||
return MG_Config::Features.DisableTimerQuery;
|
return MG_Config::Features.DisableTimerQuery;
|
||||||
@@ -204,10 +209,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BeginQuery(GLenum target, GLuint id) {
|
void BeginQuery(GLenum target, GLuint id) {
|
||||||
if (target != GL_TIME_ELAPSED) {
|
const Bool isTransformFeedbackQuery =
|
||||||
// Only GL_TIME_ELAPSED timer queries are implemented (occlusion and
|
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
||||||
// primitive queries remain stubs); GL_TIMESTAMP is not a valid
|
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery) {
|
||||||
// BeginQuery target either.
|
// 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.");
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -221,9 +228,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||||
return;
|
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__,
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
"A query is already active on GL_TIME_ELAPSED.");
|
"A query is already active on this target.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (queryObject->active) {
|
if (queryObject->active) {
|
||||||
@@ -239,25 +250,47 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
ResetQueryObjectLocked(queryObject); // discard any previous result
|
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||||
queryObject->target = target;
|
queryObject->target = target;
|
||||||
queryObject->active = true;
|
queryObject->active = true;
|
||||||
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
if (isTransformFeedbackQuery) {
|
||||||
queryObject->backendHandle =
|
// CPU accounting: captured draws bump the context counter; the query
|
||||||
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
// result is the delta between Begin and End. Without geometry-stage
|
||||||
g_activeTimeElapsedQueryId = id;
|
// 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) {
|
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.");
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
if (g_activeTimeElapsedQueryId == 0) {
|
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on GL_TIME_ELAPSED.");
|
? (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;
|
return;
|
||||||
}
|
}
|
||||||
auto* queryObject = FindQueryObjectLocked(g_activeTimeElapsedQueryId);
|
auto* queryObject = FindQueryObjectLocked(activeQueryId);
|
||||||
if (!queryObject) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
EndTimeElapsedQueryLocked(queryObject);
|
EndTimeElapsedQueryLocked(queryObject);
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ namespace MobileGL {
|
|||||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
m_transformFeedbackPrimitiveMode = primitiveMode;
|
||||||
m_transformFeedbackProgram = program;
|
m_transformFeedbackProgram = program;
|
||||||
++m_transformFeedbackGeneration;
|
++m_transformFeedbackGeneration;
|
||||||
|
m_transformFeedbackCapturedVertices = 0;
|
||||||
}
|
}
|
||||||
void EndTransformFeedback() {
|
void EndTransformFeedback() {
|
||||||
m_transformFeedbackActive = false;
|
m_transformFeedbackActive = false;
|
||||||
@@ -230,6 +231,19 @@ namespace MobileGL {
|
|||||||
// Bumped on every BeginTransformFeedback; the backend uses it to
|
// Bumped on every BeginTransformFeedback; the backend uses it to
|
||||||
// distinguish "resume appending" from "fresh capture".
|
// distinguish "resume appending" from "fresh capture".
|
||||||
Uint64 GetTransformFeedbackGeneration() const { return m_transformFeedbackGeneration; }
|
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
|
// Framebuffer
|
||||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||||
@@ -267,6 +281,8 @@ namespace MobileGL {
|
|||||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||||
Uint64 m_transformFeedbackGeneration = 0;
|
Uint64 m_transformFeedbackGeneration = 0;
|
||||||
|
Uint64 m_transformFeedbackPrimitiveCounter = 0;
|
||||||
|
Uint64 m_transformFeedbackCapturedVertices = 0;
|
||||||
TextureState m_textureState;
|
TextureState m_textureState;
|
||||||
ProgramState m_programState;
|
ProgramState m_programState;
|
||||||
RenderState m_renderState;
|
RenderState m_renderState;
|
||||||
|
|||||||
Reference in New Issue
Block a user