mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Fix, Test] (GLImpl, MG_State, DirectGLES, MG_Test): separate the transform feedback query counters and prefer the exact CPU count on DirectGLES
This commit is contained in:
@@ -236,6 +236,14 @@ namespace MobileGL {
|
||||
// (optional; null = frontend falls back to CPU accounting).
|
||||
BackendQueryHandle (*BeginXfbPrimitivesQuery)(Bool generated);
|
||||
void (*EndXfbPrimitivesQuery)(BackendQueryHandle query);
|
||||
// Whether GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN should be answered from the
|
||||
// frontend's own accounting wherever that accounting is exact - a capture with no
|
||||
// geometry stage - instead of from the query above. Set by DirectGLES, whose result
|
||||
// is whatever the ES driver's PRIMITIVES_WRITTEN counter says: Adreno reports twice
|
||||
// the written count for a vertex-only capture that follows a large render pass,
|
||||
// where the desktop-exact answer is the one the frontend already computed. Defaults
|
||||
// to false, so a backend that never sets it keeps using its GPU result.
|
||||
Bool PrefersCpuXfbPrimitiveAccounting = false;
|
||||
// Transform feedback capture spans, for backends whose own GL/ES driver
|
||||
// performs the capture (DirectGLES). Both optional; null means the backend
|
||||
// drives capture from its draw recording instead (DirectVulkan). End is
|
||||
|
||||
@@ -1166,6 +1166,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// geometry shader's amplification.
|
||||
funcsTable.GL.BeginXfbPrimitivesQuery = BeginXfbPrimitivesQuery;
|
||||
funcsTable.GL.EndXfbPrimitivesQuery = EndXfbPrimitivesQuery;
|
||||
// ...but where it CAN see the whole capture - no geometry stage - the frontend's
|
||||
// own count is the desktop-exact one and the ES driver's is only as good as the
|
||||
// vendor made it (Adreno doubles PRIMITIVES_WRITTEN for a vertex-only capture that
|
||||
// follows a large render pass). The query above stays installed: it is still what
|
||||
// answers an amplifying span, and PRIMITIVES_GENERATED always.
|
||||
funcsTable.GL.PrefersCpuXfbPrimitiveAccounting = true;
|
||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||
|
||||
@@ -108,6 +108,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||
if (program != nullptr) {
|
||||
// A geometry stage writes what it emits, not what the draw assembled, and the
|
||||
// amplification factor lives in the shader. Record that this span contained such
|
||||
// a draw so the transform feedback queries keep their backend result for it.
|
||||
if (program->GetShaderIndexByStage(ShaderStage::Geometry) >= 0) {
|
||||
MG_State::pGLContext->AddTransformFeedbackGeometryCaptureDraw();
|
||||
}
|
||||
// Capacity in captured vertices = the tightest bound buffer.
|
||||
Uint64 capacityVertices = ~0ull;
|
||||
for (SizeT i = 0; i < program->GetTransformFeedbackBufferCount(); ++i) {
|
||||
@@ -127,6 +133,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
MG_State::pGLContext->AddTransformFeedbackPrimitives(primitives);
|
||||
MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive);
|
||||
// Only draws that get this far are in the written counter at all. The instanced and
|
||||
// indirect entry points never call this function, so a span that contains one is NOT
|
||||
// fully accounted, and the queries must be able to tell: they compare this counter's
|
||||
// delta against zero before standing in for the backend's own result.
|
||||
MG_State::pGLContext->AddTransformFeedbackAccountedCaptureDraw();
|
||||
}
|
||||
|
||||
// Every primitive mode a draw command accepts (GL 4.6 core table 10.1, plus
|
||||
|
||||
@@ -31,8 +31,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
Bool ended = false;
|
||||
Bool resultCached = false;
|
||||
Uint64 cachedResult = 0;
|
||||
// Transform feedback primitive counter at BeginQuery time.
|
||||
// The transform feedback primitive counter matching this query's target, at
|
||||
// BeginQuery time.
|
||||
Uint64 counterSnapshot = 0;
|
||||
// Capture-draw counters at BeginQuery time: how many capture draws the CPU
|
||||
// accounting had reproduced exactly, and how many of those it could not (a
|
||||
// geometry stage amplifies). Their deltas decide whether the CPU result may
|
||||
// stand in for the backend's.
|
||||
Uint64 accountedCaptureDrawSnapshot = 0;
|
||||
Uint64 geometryCaptureDrawSnapshot = 0;
|
||||
};
|
||||
|
||||
// Query calls may arrive from any thread (launchers migrate the context
|
||||
@@ -122,6 +129,46 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
g_activeTimeElapsedQueryId = 0;
|
||||
}
|
||||
|
||||
// The CPU accounting counter a transform feedback query target reads: what the capture
|
||||
// buffers took for GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, and everything the capture
|
||||
// stage assembled - a paused span included - for GL_PRIMITIVES_GENERATED. One counter
|
||||
// for both targets would report the clamped written count as the generated one.
|
||||
Uint64 TransformFeedbackCounterForTarget(GLenum target) {
|
||||
return target == GL_PRIMITIVES_GENERATED
|
||||
? MG_State::pGLContext->GetTransformFeedbackGeneratedCounter()
|
||||
: MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
||||
}
|
||||
|
||||
// The span's CPU accounting delta. Saturating: a snapshot left above its counter (a
|
||||
// context switch between Begin and End, a counter that never moved) would otherwise
|
||||
// wrap to 2^64-1, which GetQueryObjectuiv hands the app as 4294967295.
|
||||
Uint64 TransformFeedbackCpuResult(const QueryObject* queryObject) {
|
||||
const Uint64 counter = TransformFeedbackCounterForTarget(queryObject->target);
|
||||
return counter > queryObject->counterSnapshot ? counter - queryObject->counterSnapshot : 0;
|
||||
}
|
||||
|
||||
// Whether this ended span's result should come from the CPU accounting rather than from
|
||||
// the backend query it also ran. Three conditions, all necessary:
|
||||
// * the backend asked for it (DirectGLES, whose ES driver counter is the unreliable
|
||||
// one; DirectVulkan never sets the bit and so is untouched by any of this);
|
||||
// * the target is PRIMITIVES_WRITTEN. GL_PRIMITIVES_GENERATED counts primitives
|
||||
// whether or not a capture is active, and the accounting only ever sees capture
|
||||
// draws, so the backend's counter is the more complete answer there;
|
||||
// * the span was fully accounted: at least one capture draw reached the accounting
|
||||
// (the instanced, indirect and multi-draw entry points do not call it at all, so a
|
||||
// span made of those is invisible to it) and none of them amplified through a
|
||||
// geometry stage, which the CPU cannot model.
|
||||
Bool PrefersCpuTransformFeedbackResult(const QueryObject* queryObject) {
|
||||
if (!MG_Backend::gBackendFunctionsTable.GL.PrefersCpuXfbPrimitiveAccounting) return false;
|
||||
if (queryObject->target != GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) return false;
|
||||
if (MG_State::pGLContext->GetTransformFeedbackGeometryCaptureDraws() !=
|
||||
queryObject->geometryCaptureDrawSnapshot) {
|
||||
return false;
|
||||
}
|
||||
return MG_State::pGLContext->GetTransformFeedbackAccountedCaptureDraws() !=
|
||||
queryObject->accountedCaptureDrawSnapshot;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -407,7 +454,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const auto beginXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.BeginXfbPrimitivesQuery;
|
||||
queryObject->backendHandle =
|
||||
beginXfbPrimitivesQuery ? beginXfbPrimitivesQuery(target == GL_PRIMITIVES_GENERATED) : nullptr;
|
||||
queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
||||
queryObject->counterSnapshot = TransformFeedbackCounterForTarget(target);
|
||||
queryObject->accountedCaptureDrawSnapshot =
|
||||
MG_State::pGLContext->GetTransformFeedbackAccountedCaptureDraws();
|
||||
queryObject->geometryCaptureDrawSnapshot =
|
||||
MG_State::pGLContext->GetTransformFeedbackGeometryCaptureDraws();
|
||||
} else if (isOcclusionQuery) {
|
||||
queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery();
|
||||
} else {
|
||||
@@ -448,12 +499,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
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;
|
||||
}
|
||||
// A backend query that is not going to be read is released here, not left to be
|
||||
// collected later: the span is over, the driver object has nothing left to say.
|
||||
// Ending it first is what makes that legal.
|
||||
if (!queryObject->backendHandle || PrefersCpuTransformFeedbackResult(queryObject)) {
|
||||
if (queryObject->backendHandle) {
|
||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||
deleteBackendQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->backendHandle = nullptr;
|
||||
}
|
||||
queryObject->cachedResult = TransformFeedbackCpuResult(queryObject);
|
||||
queryObject->resultCached = true;
|
||||
}
|
||||
// Otherwise the result comes from the GPU query at read time.
|
||||
queryObject->active = false;
|
||||
queryObject->ended = true;
|
||||
activeQueryId = 0;
|
||||
|
||||
@@ -328,6 +328,7 @@ namespace MobileGL {
|
||||
// transform feedback counter cannot see them - nothing was being captured.
|
||||
void AddTransformFeedbackPausedPrimitives(Uint64 primitives) {
|
||||
m_transformFeedbackPausedPrimitiveCounter += primitives;
|
||||
m_transformFeedbackGeneratedPrimitiveCounter += primitives;
|
||||
}
|
||||
Uint64 GetTransformFeedbackPausedPrimitiveCounter() const {
|
||||
return m_transformFeedbackPausedPrimitiveCounter;
|
||||
@@ -342,8 +343,30 @@ namespace MobileGL {
|
||||
// (pre-clamp; drives the GS strip capture-order fixup at EndTF).
|
||||
void AddTransformFeedbackInputPrimitives(Uint64 primitives) {
|
||||
m_transformFeedbackInputPrimitives += primitives;
|
||||
m_transformFeedbackGeneratedPrimitiveCounter += primitives;
|
||||
}
|
||||
Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; }
|
||||
// What a GL_PRIMITIVES_GENERATED query counts over its span: every primitive the
|
||||
// capture stage assembled, including the ones a paused span discarded (those are
|
||||
// generated but never written). Kept as its own running total rather than derived
|
||||
// from the input counter above, which BeginTransformFeedback resets per span while
|
||||
// a query may cover several of them.
|
||||
Uint64 GetTransformFeedbackGeneratedCounter() const {
|
||||
return m_transformFeedbackGeneratedPrimitiveCounter;
|
||||
}
|
||||
// Capture draws whose written-primitive count the CPU accounting reproduced
|
||||
// exactly, and the subset it could not: a program with a geometry stage amplifies
|
||||
// by whatever the shader emits, which only the driver's own counter knows. The
|
||||
// transform feedback queries diff both over their span to decide whether the CPU
|
||||
// delta may stand in for the backend's GPU result (GL_Query.cpp).
|
||||
void AddTransformFeedbackAccountedCaptureDraw() { ++m_transformFeedbackAccountedCaptureDraws; }
|
||||
Uint64 GetTransformFeedbackAccountedCaptureDraws() const {
|
||||
return m_transformFeedbackAccountedCaptureDraws;
|
||||
}
|
||||
void AddTransformFeedbackGeometryCaptureDraw() { ++m_transformFeedbackGeometryCaptureDraws; }
|
||||
Uint64 GetTransformFeedbackGeometryCaptureDraws() const {
|
||||
return m_transformFeedbackGeometryCaptureDraws;
|
||||
}
|
||||
|
||||
// Transform feedback objects (ARB_transform_feedback2 / GL 4.0 core).
|
||||
// The capture state above and the indexed GL_TRANSFORM_FEEDBACK_BUFFER
|
||||
@@ -439,6 +462,9 @@ namespace MobileGL {
|
||||
Uint64 m_transformFeedbackPausedPrimitiveCounter = 0;
|
||||
Uint64 m_transformFeedbackCapturedVertices = 0;
|
||||
Uint64 m_transformFeedbackInputPrimitives = 0;
|
||||
Uint64 m_transformFeedbackGeneratedPrimitiveCounter = 0;
|
||||
Uint64 m_transformFeedbackAccountedCaptureDraws = 0;
|
||||
Uint64 m_transformFeedbackGeometryCaptureDraws = 0;
|
||||
|
||||
// Everything a transform feedback object owns while it is NOT the bound one.
|
||||
struct TransformFeedbackObjectState {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
#include <MG_Impl/GLImpl/Query/GL_Query.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
|
||||
@@ -119,6 +120,56 @@ namespace {
|
||||
g_stubResultObtainable = true;
|
||||
g_stubResultNs = 0;
|
||||
}
|
||||
|
||||
// Stub backend transform feedback primitive queries. g_stubXfbQuerySupported = false
|
||||
// models a backend with no GPU counter at all (null handle), which is what leaves the
|
||||
// frontend's CPU accounting as the only source; g_stubResultNs is what the "driver"
|
||||
// would answer when its query IS read, deliberately set to a value the CPU accounting
|
||||
// never produces so the two sources are told apart.
|
||||
Int g_stubXfbBeginCount = 0;
|
||||
Int g_stubXfbEndCount = 0;
|
||||
Bool g_stubXfbQuerySupported = true;
|
||||
|
||||
MG_Backend::BackendQueryHandle StubBeginXfbPrimitivesQuery(Bool) {
|
||||
if (!g_stubXfbQuerySupported) {
|
||||
return nullptr;
|
||||
}
|
||||
++g_stubXfbBeginCount;
|
||||
return reinterpret_cast<MG_Backend::BackendQueryHandle>(static_cast<uintptr_t>(0x53));
|
||||
}
|
||||
|
||||
void StubEndXfbPrimitivesQuery(MG_Backend::BackendQueryHandle) { ++g_stubXfbEndCount; }
|
||||
|
||||
void InstallStubBackendXfbQueries() {
|
||||
auto& backendGL = MG_Backend::gBackendFunctionsTable.GL;
|
||||
backendGL.BeginXfbPrimitivesQuery = StubBeginXfbPrimitivesQuery;
|
||||
backendGL.EndXfbPrimitivesQuery = StubEndXfbPrimitivesQuery;
|
||||
backendGL.IsQueryResultAvailable = StubIsQueryResultAvailable;
|
||||
backendGL.GetQueryResult64 = StubGetQueryResult64;
|
||||
backendGL.DeleteBackendQuery = StubDeleteBackendQuery;
|
||||
// Off by default: the tests that exercise the DirectGLES preference turn it on.
|
||||
backendGL.PrefersCpuXfbPrimitiveAccounting = false;
|
||||
g_stubXfbBeginCount = 0;
|
||||
g_stubXfbEndCount = 0;
|
||||
g_stubXfbQuerySupported = true;
|
||||
g_stubDeleteCount = 0;
|
||||
g_stubResultAvailable = true;
|
||||
g_stubResultObtainable = true;
|
||||
g_stubResultNs = 0;
|
||||
}
|
||||
|
||||
// What AccountTransformFeedbackPrimitives (GL_Drawing.cpp) records for one captured
|
||||
// draw, without needing a draw: `assembled` primitives came out of the vertex stage
|
||||
// and `written` of them fitted in the capture buffers (they differ once the buffers
|
||||
// overflow, which is the whole point of PRIMITIVES_WRITTEN).
|
||||
void SimulateAccountedCaptureDraw(Uint64 assembled, Uint64 written, Bool throughGeometryStage = false) {
|
||||
MG_State::pGLContext->AddTransformFeedbackInputPrimitives(assembled);
|
||||
if (throughGeometryStage) {
|
||||
MG_State::pGLContext->AddTransformFeedbackGeometryCaptureDraw();
|
||||
}
|
||||
MG_State::pGLContext->AddTransformFeedbackPrimitives(written);
|
||||
MG_State::pGLContext->AddTransformFeedbackAccountedCaptureDraw();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class QueryTest : public ::testing::Test {
|
||||
@@ -448,6 +499,178 @@ TEST_F(QueryTest, BackendResultsPropagateThroughFrontend) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// The two transform feedback targets count different things and must therefore read
|
||||
// different counters: PRIMITIVES_WRITTEN what the capture buffers took, PRIMITIVES_GENERATED
|
||||
// every primitive the capture stage assembled - including the ones a paused span threw away,
|
||||
// which are generated but never written. Answering both from the written counter (as the
|
||||
// fallback used to) reports the clamped number as the generated one.
|
||||
TEST_F(QueryTest, TransformFeedbackQueryTargetsReadTheirOwnCounter) {
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendXfbQueries();
|
||||
g_stubXfbQuerySupported = false; // no GPU counter: the CPU accounting is the only source
|
||||
|
||||
GLuint ids[2] = {0, 0};
|
||||
MG_Impl::GLImpl::GenQueries(2, ids);
|
||||
ASSERT_NE(ids[0], 0u);
|
||||
ASSERT_NE(ids[1], 0u);
|
||||
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[0]);
|
||||
MG_Impl::GLImpl::BeginQuery(GL_PRIMITIVES_GENERATED, ids[1]);
|
||||
// Four points assembled into a buffer with room for three.
|
||||
SimulateAccountedCaptureDraw(/*assembled=*/4, /*written=*/3);
|
||||
// ...and two more points assembled while the span was paused: generated, never written.
|
||||
MG_State::pGLContext->AddTransformFeedbackPausedPrimitives(2);
|
||||
MG_Impl::GLImpl::EndQuery(GL_PRIMITIVES_GENERATED);
|
||||
MG_Impl::GLImpl::EndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
||||
|
||||
GLuint written = 0;
|
||||
GLuint generated = 0;
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(ids[0], GL_QUERY_RESULT, &written);
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(ids[1], GL_QUERY_RESULT, &generated);
|
||||
EXPECT_EQ(written, 3u);
|
||||
EXPECT_EQ(generated, 6u);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DeleteQueries(2, ids);
|
||||
}
|
||||
|
||||
// A query span that captured nothing at all reads zero from the CPU accounting rather than
|
||||
// the unsigned wrap-around a bare End-minus-Begin subtraction produces the moment the
|
||||
// snapshot is not below the counter (GetQueryObjectuiv would hand the app 4294967295).
|
||||
TEST_F(QueryTest, AnEmptyTransformFeedbackSpanReadsZero) {
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendXfbQueries();
|
||||
g_stubXfbQuerySupported = false;
|
||||
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
ASSERT_NE(id, 0u);
|
||||
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, id);
|
||||
MG_Impl::GLImpl::EndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
||||
|
||||
GLuint result = 123u;
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(id, GL_QUERY_RESULT, &result);
|
||||
EXPECT_EQ(result, 0u);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DeleteQueries(1, &id);
|
||||
}
|
||||
|
||||
// The DirectGLES preference: for a capture the frontend counted exactly - every draw
|
||||
// accounted, none of them amplified by a geometry stage - the CPU number is the
|
||||
// desktop-exact one and the ES driver's PRIMITIVES_WRITTEN counter is not consulted, even
|
||||
// though the backend query ran. The backend query object is released at EndQuery instead of
|
||||
// being left to a result read that will never come.
|
||||
TEST_F(QueryTest, VertexOnlyCaptureSpansPreferTheCpuPrimitiveAccounting) {
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendXfbQueries();
|
||||
MG_Backend::gBackendFunctionsTable.GL.PrefersCpuXfbPrimitiveAccounting = true;
|
||||
g_stubResultNs = 6; // what the driver's counter would have said - twice the truth
|
||||
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
ASSERT_NE(id, 0u);
|
||||
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, id);
|
||||
SimulateAccountedCaptureDraw(/*assembled=*/4, /*written=*/3);
|
||||
MG_Impl::GLImpl::EndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
||||
EXPECT_EQ(g_stubXfbBeginCount, 1);
|
||||
EXPECT_EQ(g_stubXfbEndCount, 1);
|
||||
EXPECT_EQ(g_stubDeleteCount, 1); // ended, then released - not leaked
|
||||
|
||||
GLint available = -1;
|
||||
MG_Impl::GLImpl::GetQueryObjectiv(id, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
EXPECT_EQ(available, 1);
|
||||
|
||||
GLuint result = 0;
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(id, GL_QUERY_RESULT, &result);
|
||||
EXPECT_EQ(result, 3u);
|
||||
EXPECT_EQ(g_stubDeleteCount, 1); // the read had no handle left to release
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DeleteQueries(1, &id);
|
||||
EXPECT_EQ(g_stubDeleteCount, 1);
|
||||
}
|
||||
|
||||
// The regression gate for that preference: a capture fed by a geometry stage writes whatever
|
||||
// the shader emits, which the CPU accounting cannot model, so the backend's counter stays the
|
||||
// answer and its handle survives EndQuery to be read later.
|
||||
TEST_F(QueryTest, AGeometryStageCaptureKeepsTheBackendPrimitiveResult) {
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendXfbQueries();
|
||||
MG_Backend::gBackendFunctionsTable.GL.PrefersCpuXfbPrimitiveAccounting = true;
|
||||
g_stubResultNs = 9; // the amplified count only the driver knows
|
||||
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
ASSERT_NE(id, 0u);
|
||||
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, id);
|
||||
SimulateAccountedCaptureDraw(/*assembled=*/1, /*written=*/1, /*throughGeometryStage=*/true);
|
||||
MG_Impl::GLImpl::EndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
||||
EXPECT_EQ(g_stubDeleteCount, 0); // still to be read
|
||||
|
||||
GLuint result = 0;
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(id, GL_QUERY_RESULT, &result);
|
||||
EXPECT_EQ(result, 9u);
|
||||
EXPECT_EQ(g_stubDeleteCount, 1);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DeleteQueries(1, &id);
|
||||
}
|
||||
|
||||
// The other half of that gate: the instanced, indirect and multi-draw entry points never
|
||||
// reach the CPU accounting, so a span made of those moves no counter at all. Its delta would
|
||||
// be zero, which is not "nothing was written" - it is "nothing was counted" - and the
|
||||
// backend's result has to stand.
|
||||
TEST_F(QueryTest, ACaptureSpanTheAccountingNeverSawKeepsTheBackendResult) {
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendXfbQueries();
|
||||
MG_Backend::gBackendFunctionsTable.GL.PrefersCpuXfbPrimitiveAccounting = true;
|
||||
g_stubResultNs = 12;
|
||||
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
ASSERT_NE(id, 0u);
|
||||
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, id);
|
||||
MG_Impl::GLImpl::EndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
||||
|
||||
GLuint result = 0;
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(id, GL_QUERY_RESULT, &result);
|
||||
EXPECT_EQ(result, 12u);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DeleteQueries(1, &id);
|
||||
}
|
||||
|
||||
// GL_PRIMITIVES_GENERATED counts primitives whether or not a capture is active, while the
|
||||
// CPU accounting only ever sees capture draws - so the preference above deliberately does
|
||||
// not extend to that target, whatever the backend asked for.
|
||||
TEST_F(QueryTest, PrimitivesGeneratedKeepsTheBackendResultUnderTheCpuPreference) {
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendXfbQueries();
|
||||
MG_Backend::gBackendFunctionsTable.GL.PrefersCpuXfbPrimitiveAccounting = true;
|
||||
g_stubResultNs = 7;
|
||||
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
ASSERT_NE(id, 0u);
|
||||
|
||||
MG_Impl::GLImpl::BeginQuery(GL_PRIMITIVES_GENERATED, id);
|
||||
SimulateAccountedCaptureDraw(/*assembled=*/4, /*written=*/3);
|
||||
MG_Impl::GLImpl::EndQuery(GL_PRIMITIVES_GENERATED);
|
||||
EXPECT_EQ(g_stubDeleteCount, 0);
|
||||
|
||||
GLuint result = 0;
|
||||
MG_Impl::GLImpl::GetQueryObjectuiv(id, GL_QUERY_RESULT, &result);
|
||||
EXPECT_EQ(result, 7u);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DeleteQueries(1, &id);
|
||||
}
|
||||
|
||||
// Environment-agnostic property test for the env -> ConfigLoader -> Features
|
||||
// chain: whatever MOBILEGL_DISABLE_TIMERQUERY is set to in the environment of
|
||||
// this test process, MG_ConfigLoader::Init must have parsed it with the
|
||||
|
||||
Reference in New Issue
Block a user