// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "GL_Query.h" #include "../Getter/GL_Getter.h" #include #include #include #include namespace MobileGL::MG_Impl::GLImpl { namespace { // Frontend query object (GL_ARB_timer_query): wraps an optional backend // timer-query handle. A null backend handle (backend has no timer-query // support, timer queries are disabled by config, or the backend could // not create a query at call time) keeps a graceful fallback: the query // result is immediately available and reads as zero. struct QueryObject { GLuint id = 0; GLenum target = 0; // 0 = gen'd but never used with BeginQuery/QueryCounter // glCreateQueries makes the object outright; glGenQueries only reserves the name, // and the object appears when the name is first used (GL 4.6 core 4.2.1). Bool created = false; MG_Backend::BackendQueryHandle backendHandle = nullptr; Bool active = false; 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 // across JVM threads), so the live-object registry is mutex-guarded, // like the sync-object registry in GL_Sync.cpp. Entries left at process // shutdown are simply dropped; their backend handles die with the // backend. std::mutex g_queryObjectsMutex; UnorderedMap g_liveQueryObjects; // Monotonically increasing id allocator; ids are valid query objects // immediately after GenQueries. 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; // Id of the query active on GL_SAMPLES_PASSED (0 = none). GLuint g_activeSamplesPassedQueryId = 0; Bool TimerQueryDisabled() { return MG_Config::Features.DisableTimerQuery; } void RecordQueryError(ErrorCode code, const char* function, const char* message) { MG_State::pGLContext->RecordError(code, MakeUnique("MG_Impl/GLImpl", function, message)); } // The by-buffer query getters write the result into a buffer object instead of client // memory. Everything about the query itself - the name, whether it is still active, the // parameter - is checked by GetQueryObjectValue; what is left is the destination, so this // resolves the buffer and confirms the write lands inside it (GL 4.6 core 4.2.1). Bool ResolveQueryResultDestination(GLuint buffer, GLintptr offset, SizeT writeSize, const char* function, SharedPtr& outBuffer) { if (offset < 0) { RecordQueryError(ErrorCode::InvalidValue, function, "Offset cannot be negative."); return false; } if (!MG_State::pGLContext->ValidateBufferObject(buffer)) { RecordQueryError(ErrorCode::InvalidOperation, function, "Buffer object does not exist."); return false; } auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); if (!bufferObject) { RecordQueryError(ErrorCode::InvalidOperation, function, "Buffer object does not exist."); return false; } if (static_cast(offset) + writeSize > bufferObject->GetSize()) { RecordQueryError(ErrorCode::InvalidOperation, function, "The query result does not fit in the buffer object at this offset."); return false; } outBuffer = bufferObject; return true; } // Callers must hold g_queryObjectsMutex. QueryObject* FindQueryObjectLocked(GLuint id) { const auto it = g_liveQueryObjects.find(id); return it != g_liveQueryObjects.end() ? it->second : nullptr; } // Callers must hold g_queryObjectsMutex. Releases the backend handle // (if any) and clears any cached result, so the object can be reused. void ResetQueryObjectLocked(QueryObject* queryObject) { if (queryObject->backendHandle) { if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) { deleteBackendQuery(queryObject->backendHandle); } queryObject->backendHandle = nullptr; } queryObject->active = false; queryObject->ended = false; queryObject->resultCached = false; queryObject->cachedResult = 0; } // Callers must hold g_queryObjectsMutex. void EndTimeElapsedQueryLocked(QueryObject* queryObject) { const auto endTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.EndTimeElapsedQuery; if (endTimeElapsedQuery && queryObject->backendHandle) { endTimeElapsedQuery(queryObject->backendHandle); } queryObject->active = false; queryObject->ended = true; g_activeTimeElapsedQueryId = 0; } // Shared GetQueryObject* implementation. Returns false when an error // was recorded and no value should be written back. `outValueProduced`, when given, // additionally distinguishes "succeeded with a value" from "succeeded but the result is not // ready" - the GL_QUERY_RESULT_NO_WAIT case, where GL_ARB_query_buffer_object says the // destination is left alone rather than written with a placeholder. Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue, Bool* outValueProduced = nullptr) { if (outValueProduced) *outValueProduced = true; const std::lock_guard lock(g_queryObjectsMutex); auto* queryObject = FindQueryObjectLocked(id); if (!queryObject) { RecordQueryError(ErrorCode::InvalidOperation, function, "Query object does not exist."); return false; } if (queryObject->active) { RecordQueryError(ErrorCode::InvalidOperation, function, "Query object is still active."); return false; } switch (pname) { case GL_QUERY_TARGET: // The target a query was begun with (or created with, for glCreateQueries) - state // the object has carried all along, GL 4.6 core table 23.35. outValue = queryObject->target; return true; case GL_QUERY_RESULT_NO_WAIT: { if (queryObject->resultCached) { outValue = queryObject->cachedResult; return true; } Uint64 result = 0; const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64; if (queryObject->backendHandle && getQueryResult64 && !getQueryResult64(queryObject->backendHandle, /*wait=*/false, &result)) { // Not ready. The whole point of the no-wait form is that the caller's // destination keeps whatever it already held. if (outValueProduced) *outValueProduced = false; outValue = 0; return true; } if (queryObject->target == GL_ANY_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) { result = result != 0 ? 1 : 0; } if (queryObject->backendHandle) { if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) { deleteBackendQuery(queryObject->backendHandle); } queryObject->backendHandle = nullptr; } queryObject->cachedResult = result; queryObject->resultCached = true; outValue = result; return true; } case GL_QUERY_RESULT_AVAILABLE: { if (queryObject->resultCached || !queryObject->backendHandle) { outValue = 1; return true; } const auto isQueryResultAvailable = MG_Backend::gBackendFunctionsTable.GL.IsQueryResultAvailable; outValue = (!isQueryResultAvailable || isQueryResultAvailable(queryObject->backendHandle)) ? 1 : 0; return true; } case GL_QUERY_RESULT: { if (queryObject->resultCached) { outValue = queryObject->cachedResult; return true; } Uint64 result = 0; if (queryObject->backendHandle) { const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64; if (getQueryResult64 && !getQueryResult64(queryObject->backendHandle, /*wait=*/true, &result)) { // The backend could not produce the result YET (e.g. a // Vulkan wait refusing to block on a not-yet-submitted // frame serial). Per the documented no-stall tradeoff // this call reads 0, but the value is NOT cached and // the backend handle is kept, so a later AVAILABLE // poll / RESULT read still produces the real value. outValue = 0; return true; } // ANY_SAMPLES_PASSED* report a boolean. if (queryObject->target == GL_ANY_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) { result = result != 0 ? 1 : 0; } // Final value produced (or no GetQueryResult64 hook: the // query degrades to a zero result); the backend handle is // consumed and the value cached for later reads. if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) { deleteBackendQuery(queryObject->backendHandle); } queryObject->backendHandle = nullptr; } queryObject->cachedResult = result; queryObject->resultCached = true; outValue = result; return true; } default: RecordQueryError(ErrorCode::InvalidEnum, function, "Unsupported query object parameter."); return false; } } template void GetQueryBufferObject(GLuint id, GLuint buffer, GLenum pname, GLintptr offset, const char* function) { SharedPtr bufferObject; if (!ResolveQueryResultDestination(buffer, offset, sizeof(T), function, bufferObject)) return; Uint64 value = 0; Bool valueProduced = false; if (!GetQueryObjectValue(id, pname, function, value, &valueProduced)) return; // GL_QUERY_RESULT_NO_WAIT on a result that has not landed writes nothing at all. if (!valueProduced) return; const T narrowed = static_cast(value); bufferObject->UploadSubData({const_cast(&narrowed), sizeof(T)}, static_cast(offset)); } } // namespace void GenQueries(GLsizei n, GLuint* ids) { if (n < 0) { RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative."); return; } if (!ids) { return; } const std::lock_guard lock(g_queryObjectsMutex); for (GLsizei i = 0; i < n; ++i) { const GLuint id = g_nextQueryId++; auto* queryObject = new QueryObject; queryObject->id = id; g_liveQueryObjects[id] = queryObject; ids[i] = id; } } // glCreateQueries differs from glGenQueries in creating the objects outright, with their // target already fixed and the rest of their state at the defaults (GL 4.6 core 4.2.1). void CreateQueries(GLenum target, GLsizei n, GLuint* ids) { switch (target) { case GL_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: case GL_TIME_ELAPSED: case GL_TIMESTAMP: case GL_PRIMITIVES_GENERATED: case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: break; default: RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not accepted."); return; } if (n < 0) { RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative."); return; } if (!ids) { return; } const std::lock_guard lock(g_queryObjectsMutex); for (GLsizei i = 0; i < n; ++i) { const GLuint id = g_nextQueryId++; auto* queryObject = new QueryObject; queryObject->id = id; queryObject->target = target; queryObject->created = true; g_liveQueryObjects[id] = queryObject; ids[i] = id; } } void DeleteQueries(GLsizei n, const GLuint* ids) { if (n < 0) { RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative."); return; } if (!ids) { return; } const std::lock_guard lock(g_queryObjectsMutex); for (GLsizei i = 0; i < n; ++i) { const auto it = g_liveQueryObjects.find(ids[i]); if (it == g_liveQueryObjects.end()) { continue; // unknown ids are silently ignored } QueryObject* queryObject = it->second; if (queryObject->active) { // Implicitly end before deletion, releasing the matching active slot. if (queryObject->target == GL_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) { if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery; endOcclusionQuery && queryObject->backendHandle) { endOcclusionQuery(queryObject->backendHandle); } queryObject->active = false; g_activeSamplesPassedQueryId = 0; } else if (queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || queryObject->target == GL_PRIMITIVES_GENERATED) { queryObject->active = false; (queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId : g_activePrimitivesGeneratedQueryId) = 0; } else { EndTimeElapsedQueryLocked(queryObject); } } if (queryObject->backendHandle) { if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) { deleteBackendQuery(queryObject->backendHandle); } queryObject->backendHandle = nullptr; } g_liveQueryObjects.erase(it); delete queryObject; } } void DestroyAllQueryObjects() { // Detach the registry under the lock and release it outside. Entries the app // already deleted were erased by DeleteQueries, so nothing here double-frees; // a DeleteQueries racing this sweep finds an empty registry and ignores the // names. The active-query slots and the name allocator are reset under the // same lock: query names are context-owned state, so a fresh context must // start clean instead of inheriting the dead context's allocator cursor or // a stale "a query is already active on this target" latch. UnorderedMap orphans; { const std::lock_guard lock(g_queryObjectsMutex); orphans.swap(g_liveQueryObjects); g_nextQueryId = 1; g_activeTimeElapsedQueryId = 0; g_activePrimitivesWrittenQueryId = 0; g_activePrimitivesGeneratedQueryId = 0; g_activeSamplesPassedQueryId = 0; } if (orphans.empty()) { return; } // Both backends' DeleteBackendQuery only free the heap wrapper once their GL // context/renderer is gone (generation/current-thread guards), so this is // safe after the backend has released its EGL resources - but not after the // function table itself is cleared. const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery; for (const auto& [_, queryObject] : orphans) { if (deleteBackendQuery && queryObject->backendHandle) { deleteBackendQuery(queryObject->backendHandle); } delete queryObject; } MGLOG_D("DestroyAllQueryObjects: reclaimed %zu query object(s) the app left undeleted", orphans.size()); } GLboolean IsQuery(GLuint id) { if (id == 0) { return GL_FALSE; } const std::lock_guard lock(g_queryObjectsMutex); // A name from glGenQueries is not yet a query object: it becomes one when it is first // used with BeginQuery/QueryCounter (which is what a non-zero target records), or // immediately if it came from glCreateQueries. const auto* queryObject = FindQueryObjectLocked(id); return (queryObject != nullptr && (queryObject->created || queryObject->target != 0)) ? GL_TRUE : GL_FALSE; } void BeginQuery(GLenum target, GLuint id) { const Bool isTransformFeedbackQuery = target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED; const Bool isOcclusionQuery = (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) && MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr; if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) { // GL_TIMESTAMP is not a valid BeginQuery target; the occlusion targets // need backend support. RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported."); return; } if (id == 0) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used."); return; } const std::lock_guard lock(g_queryObjectsMutex); auto* queryObject = FindQueryObjectLocked(id); if (!queryObject) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist."); return; } GLuint& activeQueryId = isTransformFeedbackQuery ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId : g_activePrimitivesGeneratedQueryId) : (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId); if (activeQueryId != 0) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "A query is already active on this target."); return; } if (queryObject->active) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is already active."); return; } if (queryObject->target != 0 && queryObject->target != target) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object was already used with a different target."); return; } ResetQueryObjectLocked(queryObject); // discard any previous result queryObject->target = target; queryObject->active = true; if (isTransformFeedbackQuery) { // Prefer real GPU transform-feedback queries (exact with geometry shaders); // the CPU accounting delta stays as the fallback when the backend lacks them. const auto beginXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.BeginXfbPrimitivesQuery; queryObject->backendHandle = beginXfbPrimitivesQuery ? beginXfbPrimitivesQuery(target == GL_PRIMITIVES_GENERATED) : nullptr; queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter(); } else if (isOcclusionQuery) { queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery(); } else { const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery; queryObject->backendHandle = (!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr; } activeQueryId = id; } void EndQuery(GLenum target) { const Bool isTransformFeedbackQuery = target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED; const Bool isOcclusionQuery = (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) && MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr; if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) { RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported."); return; } const std::lock_guard lock(g_queryObjectsMutex); GLuint& activeQueryId = isTransformFeedbackQuery ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId : g_activePrimitivesGeneratedQueryId) : (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId); if (activeQueryId == 0) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target."); return; } auto* queryObject = FindQueryObjectLocked(activeQueryId); if (!queryObject) { activeQueryId = 0; // should not happen; keep state consistent return; } if (isTransformFeedbackQuery) { if (queryObject->backendHandle) { if (const auto endXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.EndXfbPrimitivesQuery) { endXfbPrimitivesQuery(queryObject->backendHandle); } // Result comes from the GPU query at read time. } else { queryObject->cachedResult = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter() - queryObject->counterSnapshot; queryObject->resultCached = true; } queryObject->active = false; queryObject->ended = true; activeQueryId = 0; return; } if (isOcclusionQuery) { if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery; endOcclusionQuery && queryObject->backendHandle) { endOcclusionQuery(queryObject->backendHandle); } queryObject->active = false; queryObject->ended = true; activeQueryId = 0; return; } EndTimeElapsedQueryLocked(queryObject); } void QueryCounter(GLuint id, GLenum target) { if (target != GL_TIMESTAMP) { RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "QueryCounter target must be GL_TIMESTAMP."); return; } if (id == 0) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used."); return; } const std::lock_guard lock(g_queryObjectsMutex); auto* queryObject = FindQueryObjectLocked(id); if (!queryObject) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist."); return; } if (queryObject->active) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is currently active."); return; } if (queryObject->target != 0 && queryObject->target != target) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object was already used with a different target."); return; } ResetQueryObjectLocked(queryObject); // discard any previous result queryObject->target = target; const auto queryCounterTimestamp = MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp; queryObject->backendHandle = (!TimerQueryDisabled() && queryCounterTimestamp) ? queryCounterTimestamp() : nullptr; queryObject->ended = true; } void GetQueryiv(GLenum target, GLenum pname, GLint* params) { if (!params) { return; } switch (pname) { case GL_CURRENT_QUERY: { const std::lock_guard lock(g_queryObjectsMutex); switch (target) { case GL_TIME_ELAPSED: *params = static_cast(g_activeTimeElapsedQueryId); break; case GL_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: *params = static_cast(g_activeSamplesPassedQueryId); break; case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: *params = static_cast(g_activePrimitivesWrittenQueryId); break; case GL_PRIMITIVES_GENERATED: *params = static_cast(g_activePrimitivesGeneratedQueryId); break; default: *params = 0; break; } return; } case GL_QUERY_COUNTER_BITS: { // 64 bits are advertised only while the live backend can actually // time: IsTimerQuerySupported is the dynamic truth (extension / // entry points / timestamp valid bits at call time, not at table // init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always // wins. if (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) { const Bool occlusionSupported = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr; *params = occlusionSupported ? (target == GL_SAMPLES_PASSED ? 32 : 1) : 0; return; } const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP; const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported; const Bool supported = timerTarget && !TimerQueryDisabled() && isTimerQuerySupported && isTimerQuerySupported(); *params = supported ? 64 : 0; return; } default: RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Unsupported query parameter."); return; } } void GetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { GetQueryBufferObject(id, buffer, pname, offset, __FUNCTION__); } void GetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { GetQueryBufferObject(id, buffer, pname, offset, __FUNCTION__); } void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { GetQueryBufferObject(id, buffer, pname, offset, __FUNCTION__); } void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { GetQueryBufferObject(id, buffer, pname, offset, __FUNCTION__); } void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params) { Uint64 value = 0; Bool valueProduced = false; if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) { return; } constexpr Uint64 kMaxInt = static_cast(INT_MAX); *params = value > kMaxInt ? INT_MAX : static_cast(value); } void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) { Uint64 value = 0; Bool valueProduced = false; if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) { return; } *params = static_cast(value & 0xFFFFFFFFull); } void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) { Uint64 value = 0; Bool valueProduced = false; if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) { return; } *params = static_cast(value); } void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) { Uint64 value = 0; Bool valueProduced = false; if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) { return; } *params = static_cast(value); } namespace { // The indexed query entry points differ from the plain ones only in the vertex // stream they address (GL 4.6 core 4.2.1): index must be below GL_MAX_VERTEX_STREAMS // for the two transform feedback targets and zero for every other target. With a // single vertex stream both bounds are 1, so a valid call is always index 0 and // forwards to the unindexed implementation. Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) { const Bool perStreamTarget = target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN; GLint maxVertexStreams = 1; if (perStreamTarget) { GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams); } if (index < static_cast(std::max(maxVertexStreams, 1))) { return true; } RecordQueryError(ErrorCode::InvalidValue, function, perStreamTarget ? "index is not less than GL_MAX_VERTEX_STREAMS." : "index must be zero for this query target."); return false; } } // namespace void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) { if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return; BeginQuery(target, id); } void EndQueryIndexed(GLenum target, GLuint index) { if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return; EndQuery(target); } void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params) { if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return; GetQueryiv(target, pname, params); } } // namespace MobileGL::MG_Impl::GLImpl