mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (DirectGLES): exact transform feedback primitive queries
Two leftovers from the capture passthrough, both only observable with a
geometry shader in the pipeline:
- GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED fell back
to the frontend's CPU accounting, which counts the primitives the draw call
assembles and so cannot see a geometry stage's amplification. Both are core ES
query targets (GL_PRIMITIVES_GENERATED from 3.2 on, gated accordingly so an
older driver doesn't get a stray GL_INVALID_ENUM), so they now go straight to
the driver's own counters. Generalized the occlusion-query handle's isOcclusion
flag into the glBeginQuery target it already had to remember for glEndQuery,
which is what tells the result read to use the core 32-bit getter.
- FixupGsStripCaptureOrder rewrites captured strip triangles from Vulkan's
(i, i+1, i+2) order into GL's (i+1, i, i+2). A driver-side capture already
emits GL order, so the rewrite corrupted it - KHR-GL33.transform_feedback
.geometry read back the odd triangle rotated one vertex. Skipped when the
backend owns the capture span.
KHR-GL3{0,1,2,3}.transform_feedback is now 21/21 on Espryt; DirectVulkan
(lavapipe) re-verified at 21/21 for the shared frontend change.
This commit is contained in:
@@ -942,6 +942,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// when the timer-query group above is disabled.
|
// when the timer-query group above is disabled.
|
||||||
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
|
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
|
||||||
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
|
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
|
||||||
|
// Real driver primitive counters: the frontend's CPU accounting cannot see a
|
||||||
|
// geometry shader's amplification.
|
||||||
|
funcsTable.GL.BeginXfbPrimitivesQuery = BeginXfbPrimitivesQuery;
|
||||||
|
funcsTable.GL.EndXfbPrimitivesQuery = EndXfbPrimitivesQuery;
|
||||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||||
|
|||||||
@@ -4473,11 +4473,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
struct GLESQueryObject {
|
struct GLESQueryObject {
|
||||||
GLuint queryId = 0;
|
GLuint queryId = 0;
|
||||||
Uint contextGeneration = 0;
|
Uint contextGeneration = 0;
|
||||||
// GL_ANY_SAMPLES_PASSED result is 0/1 and only ever reachable through the
|
// Non-zero for the core (non-timer) query targets - occlusion and transform
|
||||||
// core (non-extension) glGetQueryObjectuiv getter - GL_EXT_disjoint_timer_query's
|
// feedback primitives - and then holds the glBeginQuery target, which glEndQuery
|
||||||
// 64-bit glGetQueryObjectui64vEXT is timer-specific and may be entirely absent
|
// needs back. Their results are counts reachable only through the core
|
||||||
// on drivers that otherwise fully support core ES3 occlusion queries.
|
// glGetQueryObjectuiv getter: GL_EXT_disjoint_timer_query's 64-bit
|
||||||
Bool isOcclusion = false;
|
// glGetQueryObjectui64vEXT is timer-specific and may be entirely absent on
|
||||||
|
// drivers that otherwise fully support these core ES queries.
|
||||||
|
GLenum coreTarget = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4670,15 +4672,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return new GLESQueryObject{queryId, g_syncContextGeneration};
|
return new GLESQueryObject{queryId, g_syncContextGeneration};
|
||||||
}
|
}
|
||||||
|
|
||||||
// GL_SAMPLES_PASSED/GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) occlusion queries. Unlike the
|
// The core (non-timer) query targets - occlusion and transform feedback primitives.
|
||||||
// timer queries above, these are core ES 3.0 (no GL_EXT_disjoint_timer_query needed).
|
// Unlike the timer queries above these are core ES (no GL_EXT_disjoint_timer_query
|
||||||
Bool AreOcclusionQueriesSupported() {
|
// needed), so they share one begin/end pair keyed on the glBeginQuery target.
|
||||||
|
static Bool AreCoreQueriesSupported() {
|
||||||
return g_GLESFuncs.glGenQueries && g_GLESFuncs.glDeleteQueries && g_GLESFuncs.glBeginQuery &&
|
return g_GLESFuncs.glGenQueries && g_GLESFuncs.glDeleteQueries && g_GLESFuncs.glBeginQuery &&
|
||||||
g_GLESFuncs.glEndQuery && g_GLESFuncs.glGetQueryObjectuiv;
|
g_GLESFuncs.glEndQuery && g_GLESFuncs.glGetQueryObjectuiv;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendQueryHandle BeginOcclusionQuery() {
|
static BackendQueryHandle BeginCoreQuery(GLenum target) {
|
||||||
if (!IsBackendContextCurrentOnThisThread() || !AreOcclusionQueriesSupported()) {
|
if (!IsBackendContextCurrentOnThisThread() || !AreCoreQueriesSupported()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
GLuint queryId = 0;
|
GLuint queryId = 0;
|
||||||
@@ -4686,22 +4689,47 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (queryId == 0) {
|
if (queryId == 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
g_GLESFuncs.glBeginQuery(target, queryId);
|
||||||
|
return new GLESQueryObject{queryId, g_syncContextGeneration, target};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndCoreQuery(BackendQueryHandle handle) {
|
||||||
|
const auto* query = static_cast<GLESQueryObject*>(handle);
|
||||||
|
if (query == nullptr || query->coreTarget == 0 ||
|
||||||
|
query->contextGeneration != g_syncContextGeneration || !IsBackendContextCurrentOnThisThread() ||
|
||||||
|
!g_GLESFuncs.glEndQuery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_GLESFuncs.glEndQuery(query->coreTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool AreOcclusionQueriesSupported() { return AreCoreQueriesSupported(); }
|
||||||
|
|
||||||
|
BackendQueryHandle BeginOcclusionQuery() {
|
||||||
// ES only implements the boolean ANY_SAMPLES_PASSED variant, not an exact
|
// ES only implements the boolean ANY_SAMPLES_PASSED variant, not an exact
|
||||||
// GL_SAMPLES_PASSED count; the frontend already coerces ANY_SAMPLES_PASSED*
|
// GL_SAMPLES_PASSED count; the frontend already coerces ANY_SAMPLES_PASSED*
|
||||||
// targets to boolean, and desktop GL_SAMPLES_PASSED reads a 0/1 approximation.
|
// targets to boolean, and desktop GL_SAMPLES_PASSED reads a 0/1 approximation.
|
||||||
g_GLESFuncs.glBeginQuery(GL_ANY_SAMPLES_PASSED, queryId);
|
return BeginCoreQuery(GL_ANY_SAMPLES_PASSED);
|
||||||
return new GLESQueryObject{queryId, g_syncContextGeneration, /*isOcclusion=*/true};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EndOcclusionQuery(BackendQueryHandle handle) {
|
void EndOcclusionQuery(BackendQueryHandle handle) { EndCoreQuery(handle); }
|
||||||
const auto* query = static_cast<GLESQueryObject*>(handle);
|
|
||||||
if (query == nullptr || query->contextGeneration != g_syncContextGeneration ||
|
// GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED. The frontend
|
||||||
!IsBackendContextCurrentOnThisThread() || !g_GLESFuncs.glEndQuery) {
|
// otherwise counts primitives on the CPU from the draw calls, which cannot see a
|
||||||
return;
|
// geometry shader's amplification; the real driver's counters are exact. Returning
|
||||||
|
// null keeps that CPU accounting as the fallback.
|
||||||
|
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated) {
|
||||||
|
// GL_PRIMITIVES_GENERATED is only a legal query target from ES 3.2 on (it comes
|
||||||
|
// with geometry shaders); issuing it earlier just leaves a stray GL_INVALID_ENUM
|
||||||
|
// that some later unrelated glGetError would report as its own failure.
|
||||||
|
if (generated && g_GLESCapabilities.GLESVersion.Major * 10 + g_GLESCapabilities.GLESVersion.Minor < 32) {
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
g_GLESFuncs.glEndQuery(GL_ANY_SAMPLES_PASSED);
|
return BeginCoreQuery(generated ? GL_PRIMITIVES_GENERATED : GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EndXfbPrimitivesQuery(BackendQueryHandle handle) { EndCoreQuery(handle); }
|
||||||
|
|
||||||
Bool IsQueryResultAvailable(BackendQueryHandle handle) {
|
Bool IsQueryResultAvailable(BackendQueryHandle handle) {
|
||||||
const auto* query = static_cast<GLESQueryObject*>(handle);
|
const auto* query = static_cast<GLESQueryObject*>(handle);
|
||||||
// Null/stale handles report available so the frontend proceeds to
|
// Null/stale handles report available so the frontend proceeds to
|
||||||
@@ -4729,7 +4757,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// the handle.
|
// the handle.
|
||||||
if (query == nullptr || query->contextGeneration != g_syncContextGeneration ||
|
if (query == nullptr || query->contextGeneration != g_syncContextGeneration ||
|
||||||
!g_GLESFuncs.glGetQueryObjectuiv ||
|
!g_GLESFuncs.glGetQueryObjectuiv ||
|
||||||
(!query->isOcclusion && !g_GLESFuncs.glGetQueryObjectui64vEXT)) {
|
(query->coreTarget == 0 && !g_GLESFuncs.glGetQueryObjectui64vEXT)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// A thread that does not own the ES context cannot issue GL calls,
|
// A thread that does not own the ES context cannot issue GL calls,
|
||||||
@@ -4767,7 +4795,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// context switch) the result may be garbage, which is tolerable for
|
// context switch) the result may be garbage, which is tolerable for
|
||||||
// an F3 GPU% readout, and consuming the latched flag here could hide
|
// an F3 GPU% readout, and consuming the latched flag here could hide
|
||||||
// the event from another observer.
|
// the event from another observer.
|
||||||
if (query->isOcclusion) {
|
if (query->coreTarget != 0) {
|
||||||
GLuint result32 = 0;
|
GLuint result32 = 0;
|
||||||
g_GLESFuncs.glGetQueryObjectuiv(query->queryId, GL_QUERY_RESULT, &result32);
|
g_GLESFuncs.glGetQueryObjectuiv(query->queryId, GL_QUERY_RESULT, &result32);
|
||||||
*outNanoseconds = static_cast<Uint64>(result32);
|
*outNanoseconds = static_cast<Uint64>(result32);
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// flow through GetQueryResult64/DeleteBackendQuery like the timer queries above.
|
// flow through GetQueryResult64/DeleteBackendQuery like the timer queries above.
|
||||||
BackendQueryHandle BeginOcclusionQuery();
|
BackendQueryHandle BeginOcclusionQuery();
|
||||||
void EndOcclusionQuery(BackendQueryHandle query);
|
void EndOcclusionQuery(BackendQueryHandle query);
|
||||||
|
// GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED, also core ES
|
||||||
|
// (GL_PRIMITIVES_GENERATED from ES 3.2 on). Null when the target is unavailable, in
|
||||||
|
// which case the frontend falls back to counting primitives from the draw calls.
|
||||||
|
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated);
|
||||||
|
void EndXfbPrimitivesQuery(BackendQueryHandle query);
|
||||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||||
// Returns true when a final value landed in *outNanoseconds (a zero for
|
// Returns true when a final value landed in *outNanoseconds (a zero for
|
||||||
// null or stale-generation handles IS final: the frontend may cache it
|
// null or stale-generation handles IS final: the frontend may cache it
|
||||||
|
|||||||
@@ -590,6 +590,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// vertex records of every odd triangle within each emitted strip.
|
// vertex records of every odd triangle within each emitted strip.
|
||||||
static void FixupGsStripCaptureOrder(const SharedPtr<MG_State::GLState::ProgramObject>& program,
|
static void FixupGsStripCaptureOrder(const SharedPtr<MG_State::GLState::ProgramObject>& program,
|
||||||
Uint64 inputPrimitives) {
|
Uint64 inputPrimitives) {
|
||||||
|
// Only Vulkan-order captures need this. A backend that runs the capture on its
|
||||||
|
// own GL/ES driver (it owns the span, hence the EndTransformFeedback entry) has
|
||||||
|
// already produced GL's vertex order, and reordering it again would corrupt it.
|
||||||
|
if (MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback != nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) {
|
if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user