[Feat] (DirectGLES): implement GL_ANY_SAMPLES_PASSED occlusion queries

DirectGLES never registered BeginOcclusionQuery/EndOcclusionQuery, so
the frontend rejected the occlusion query targets entirely; the CTS
tests that use them (e.g. packed_depth_stencil.verify_partial/mixed_
attachments) left a stray GL_INVALID_ENUM that a later, unrelated
glGetError() check would report as its own failure
("Uploading buffer data failed" at gluDrawUtil.cpp:363).

Occlusion queries are core ES3 (glGenQueries/glBeginQuery(GL_ANY_
SAMPLES_PASSED, ...)/glEndQuery/glGetQueryObjectuiv), unlike the timer
queries which need GL_EXT_disjoint_timer_query, so they're wired up
unconditionally (independent of MOBILEGL_DISABLE_TIMERQUERY) using the
same handle-based GetQueryResult64/DeleteBackendQuery plumbing already
shared with timer queries. GetQueryResult64 now reads the 0/1 result
through the core 32-bit glGetQueryObjectuiv getter for occlusion
handles instead of the timer-only 64-bit GL_EXT_disjoint_timer_query
getter, since a driver can fully support core occlusion queries while
lacking that extension entirely.
This commit is contained in:
BZLZHH
2026-08-01 11:20:38 -04:00
parent ac81185968
commit 0a5d7ceb6c
3 changed files with 58 additions and 4 deletions
@@ -935,11 +935,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
funcsTable.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
funcsTable.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
funcsTable.GL.QueryCounterTimestamp = QueryCounterTimestamp;
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
}
// Occlusion queries are core ES3 (independent of MOBILEGL_DISABLE_TIMERQUERY)
// and share the handle-based result/delete entries, which must exist even
// when the timer-query group above is disabled.
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
funcsTableInitialized = true;
}
return funcsTable;
+45 -1
View File
@@ -4360,6 +4360,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
struct GLESQueryObject {
GLuint queryId = 0;
Uint contextGeneration = 0;
// GL_ANY_SAMPLES_PASSED result is 0/1 and only ever reachable through the
// core (non-extension) glGetQueryObjectuiv getter - GL_EXT_disjoint_timer_query's
// 64-bit glGetQueryObjectui64vEXT is timer-specific and may be entirely absent
// on drivers that otherwise fully support core ES3 occlusion queries.
Bool isOcclusion = false;
};
}
@@ -4552,6 +4557,38 @@ namespace MobileGL::MG_Backend::DirectGLES {
return new GLESQueryObject{queryId, g_syncContextGeneration};
}
// GL_SAMPLES_PASSED/GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) occlusion queries. Unlike the
// timer queries above, these are core ES 3.0 (no GL_EXT_disjoint_timer_query needed).
Bool AreOcclusionQueriesSupported() {
return g_GLESFuncs.glGenQueries && g_GLESFuncs.glDeleteQueries && g_GLESFuncs.glBeginQuery &&
g_GLESFuncs.glEndQuery && g_GLESFuncs.glGetQueryObjectuiv;
}
BackendQueryHandle BeginOcclusionQuery() {
if (!IsBackendContextCurrentOnThisThread() || !AreOcclusionQueriesSupported()) {
return nullptr;
}
GLuint queryId = 0;
g_GLESFuncs.glGenQueries(1, &queryId);
if (queryId == 0) {
return nullptr;
}
// ES only implements the boolean ANY_SAMPLES_PASSED variant, not an exact
// GL_SAMPLES_PASSED count; the frontend already coerces ANY_SAMPLES_PASSED*
// targets to boolean, and desktop GL_SAMPLES_PASSED reads a 0/1 approximation.
g_GLESFuncs.glBeginQuery(GL_ANY_SAMPLES_PASSED, queryId);
return new GLESQueryObject{queryId, g_syncContextGeneration, /*isOcclusion=*/true};
}
void EndOcclusionQuery(BackendQueryHandle handle) {
const auto* query = static_cast<GLESQueryObject*>(handle);
if (query == nullptr || query->contextGeneration != g_syncContextGeneration ||
!IsBackendContextCurrentOnThisThread() || !g_GLESFuncs.glEndQuery) {
return;
}
g_GLESFuncs.glEndQuery(GL_ANY_SAMPLES_PASSED);
}
Bool IsQueryResultAvailable(BackendQueryHandle handle) {
const auto* query = static_cast<GLESQueryObject*>(handle);
// Null/stale handles report available so the frontend proceeds to
@@ -4578,7 +4615,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// report it as produced and let the frontend cache it and release
// the handle.
if (query == nullptr || query->contextGeneration != g_syncContextGeneration ||
!g_GLESFuncs.glGetQueryObjectuiv || !g_GLESFuncs.glGetQueryObjectui64vEXT) {
!g_GLESFuncs.glGetQueryObjectuiv ||
(!query->isOcclusion && !g_GLESFuncs.glGetQueryObjectui64vEXT)) {
return true;
}
// A thread that does not own the ES context cannot issue GL calls,
@@ -4616,6 +4654,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// context switch) the result may be garbage, which is tolerable for
// an F3 GPU% readout, and consuming the latched flag here could hide
// the event from another observer.
if (query->isOcclusion) {
GLuint result32 = 0;
g_GLESFuncs.glGetQueryObjectuiv(query->queryId, GL_QUERY_RESULT, &result32);
*outNanoseconds = static_cast<Uint64>(result32);
return true;
}
GLuint64 result = 0;
g_GLESFuncs.glGetQueryObjectui64vEXT(query->queryId, GL_QUERY_RESULT, &result);
*outNanoseconds = static_cast<Uint64>(result);
@@ -130,6 +130,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
BackendQueryHandle BeginTimeElapsedQuery();
void EndTimeElapsedQuery(BackendQueryHandle query);
BackendQueryHandle QueryCounterTimestamp();
// GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) occlusion queries: core ES3, independent of
// GL_EXT_disjoint_timer_query and of MOBILEGL_DISABLE_TIMERQUERY. Results/deletion
// flow through GetQueryResult64/DeleteBackendQuery like the timer queries above.
BackendQueryHandle BeginOcclusionQuery();
void EndOcclusionQuery(BackendQueryHandle query);
Bool IsQueryResultAvailable(BackendQueryHandle query);
// Returns true when a final value landed in *outNanoseconds (a zero for
// null or stale-generation handles IS final: the frontend may cache it