From 0a5d7ceb6cff414d4634fe116336dd5703ff0e1e Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Sat, 1 Aug 2026 11:20:38 -0400 Subject: [PATCH] [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. --- .../DirectGLES/BackendObject_DirectGLES.cpp | 11 +++-- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 46 ++++++++++++++++++- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 5 ++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index c0aedea2..11f368cd 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -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; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index c27911ae..9e889fb1 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -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(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(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(result32); + return true; + } GLuint64 result = 0; g_GLESFuncs.glGetQueryObjectui64vEXT(query->queryId, GL_QUERY_RESULT, &result); *outNanoseconds = static_cast(result); diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 31edfc09..4b47e0b5 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -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