[Feat, Test] (GLImpl, GLState): implement glBeginConditionalRender and discard the commands GL 4.6 10.9 names

This commit is contained in:
2026-08-20 12:59:22 -04:00
parent 6b1d89f279
commit 1740a8a41a
7 changed files with 278 additions and 2 deletions
@@ -326,10 +326,23 @@ namespace MobileGL::MG_Impl::GLImpl {
}
}
// GL 4.6 core 10.9: inside a conditional block whose predicate did not pass, the drawing
// commands, Clear, ClearBuffer* and the compute dispatches are DISCARDED. The gate sits on the
// wrappers that ISSUE the backend call rather than at the top of each entry point, so that
// everything a real driver would still do inside the block - argument validation and the
// errors it raises - happens exactly as it does outside one, and only the command itself is
// dropped. It is deliberately not on the frontend's transform-feedback accounting either:
// that mirrors what the capture stage would have written, and a conditional block around a
// capturing draw has no test coverage in either direction.
static Bool ConditionalRenderDiscardsCommand() {
return MG_State::pGLContext->ConditionalRenderDiscardsCommands();
}
void Clear_Backend(GLbitfield mask) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.Clear(mask);
}
@@ -337,6 +350,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElements(mode, count, type, indices);
}
@@ -345,6 +359,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElements(mode, count, type, indices, drawcount);
}
@@ -353,6 +368,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsBaseVertex(mode, count, type, indices, drawcount,
basevertex);
}
@@ -361,6 +377,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
}
@@ -368,6 +385,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArrays(mode, first, count, drawcount);
}
@@ -376,6 +394,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElementsBaseVertex(mode, count, type, indices, basevertex);
}
@@ -384,6 +403,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@@ -391,6 +411,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride);
}
@@ -399,6 +420,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount(mode, type, indirect, drawcount,
maxdrawcount, stride);
}
@@ -408,6 +430,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount(mode, indirect, drawcount, maxdrawcount,
stride);
}
@@ -417,6 +440,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawRangeElementsBaseVertex(mode, start, end, count, type, indices,
basevertex);
}
@@ -426,6 +450,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawRangeElements(mode, start, end, count, type, indices);
}
@@ -435,6 +460,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseVertexBaseInstance(
mode, count, type, indices, instancecount, basevertex, baseinstance);
}
@@ -444,6 +470,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount,
basevertex);
}
@@ -453,6 +480,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseInstance(mode, count, type, indices,
instancecount, baseinstance);
}
@@ -462,6 +490,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstanced(mode, count, type, indices, instancecount);
}
@@ -469,6 +498,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawElementsIndirect(mode, type, indirect);
}
void DrawArraysInstancedBaseInstance_Backend(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
@@ -476,6 +506,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawArraysInstancedBaseInstance(mode, first, count, instancecount,
baseinstance);
}
@@ -484,6 +515,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawArraysInstanced(mode, first, count, instancecount);
}
@@ -491,6 +523,7 @@ namespace MobileGL::MG_Impl::GLImpl {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (ConditionalRenderDiscardsCommand()) return;
MG_Backend::gBackendFunctionsTable.GL.DrawArraysIndirect(mode, indirect);
}
@@ -519,6 +552,9 @@ namespace MobileGL::MG_Impl::GLImpl {
return;
}
}
// GL 4.3 added both dispatches to the conditional-render set (GL 4.6 core 10.9), which is
// exactly what KHR-GL43.compute_shader.conditional-dispatching checks.
if (ConditionalRenderDiscardsCommand()) return;
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
}
@@ -570,6 +606,7 @@ namespace MobileGL::MG_Impl::GLImpl {
return;
}
if (!ValidateCurrentProgramForCompute(__func__)) return;
if (ConditionalRenderDiscardsCommand()) return;
dispatchComputeIndirect(indirect);
}
@@ -725,8 +725,8 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadName, GLuint name) DECLARE_GL_FUNCTION_S
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushName, GLuint name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushName, name)
DECLARE_GL_FUNCTION_STUB_HEAD(void, PopName) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopName)
DECLARE_GL_FUNCTION_HEAD(void, ClampColor, GLenum target, GLenum clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClampColor, target, clamp)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginConditionalRender, id, mode)
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndConditionalRender, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndConditionalRender)
DECLARE_GL_FUNCTION_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginConditionalRender, id, mode)
DECLARE_GL_FUNCTION_HEAD(void, EndConditionalRender) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndConditionalRender)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1i, GLuint index, GLint x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1i, index, x)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2i, GLuint index, GLint x, GLint y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2i, index, x, y)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3i, GLuint index, GLint x, GLint y, GLint z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3i, index, x, y, z)
@@ -2613,18 +2613,26 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void ClearBufferfi_Backend(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
MG_Backend::gBackendFunctionsTable.GL.ClearBufferfi(buffer, drawbuffer, depth, stencil);
}
void ClearBufferfv_Backend(GLenum buffer, GLint drawbuffer, const GLfloat* value) {
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
MG_Backend::gBackendFunctionsTable.GL.ClearBufferfv(buffer, drawbuffer, value);
}
void ClearBufferuiv_Backend(GLenum buffer, GLint drawbuffer, const GLuint* value) {
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
MG_Backend::gBackendFunctionsTable.GL.ClearBufferuiv(buffer, drawbuffer, value);
}
void ClearBufferiv_Backend(GLenum buffer, GLint drawbuffer, const GLint* value) {
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
MG_Backend::gBackendFunctionsTable.GL.ClearBufferiv(buffer, drawbuffer, value);
}
@@ -565,6 +565,75 @@ namespace MobileGL::MG_Impl::GLImpl {
queryObject->ended = true;
}
void BeginConditionalRender(GLuint id, GLenum mode) {
// GL 4.6 core 10.9's eight modes. The _INVERTED half flips the sense of the predicate;
// the BY_REGION half only narrows WHERE an implementation is permitted to discard, so
// treating it as its whole-framebuffer sibling is what an implementation without region
// granularity does. The _NO_WAIT half is a permission to render rather than stall, not an
// obligation - see the resolve below.
Bool inverted = false;
switch (mode) {
case GL_QUERY_WAIT:
case GL_QUERY_NO_WAIT:
case GL_QUERY_BY_REGION_WAIT:
case GL_QUERY_BY_REGION_NO_WAIT:
inverted = false;
break;
case GL_QUERY_WAIT_INVERTED:
case GL_QUERY_NO_WAIT_INVERTED:
case GL_QUERY_BY_REGION_WAIT_INVERTED:
case GL_QUERY_BY_REGION_NO_WAIT_INVERTED:
inverted = true;
break;
default:
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "mode is not a conditional render mode.");
return;
}
if (MG_State::pGLContext->IsConditionalRenderActive()) {
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Conditional rendering is already active.");
return;
}
{
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
const auto* queryObject = FindQueryObjectLocked(id);
// A generated NAME is not yet a query object; it becomes one at its first use with a
// target (the same rule glIsQuery answers by).
if (!queryObject || (!queryObject->created && queryObject->target == 0)) {
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "id is not the name of a query object.");
return;
}
if (queryObject->active) {
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "The query object is still active.");
return;
}
if (queryObject->target != GL_SAMPLES_PASSED && queryObject->target != GL_ANY_SAMPLES_PASSED &&
queryObject->target != GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
"Conditional rendering requires an occlusion query object.");
return;
}
}
// Resolved ONCE, here, and by WAITING even for the _NO_WAIT modes: the spec lets those
// render instead of stalling, so always waiting is conforming and is the only choice that
// gives the whole block one deterministic verdict. Reading it per command instead would
// let a result that lands mid-block change the answer half way through.
Uint64 samplesPassed = 0;
if (!GetQueryObjectValue(id, GL_QUERY_RESULT, __FUNCTION__, samplesPassed)) return;
const Bool passed = samplesPassed != 0;
MG_State::pGLContext->BeginConditionalRender(id, mode, inverted ? passed : !passed);
}
void EndConditionalRender() {
if (!MG_State::pGLContext->IsConditionalRenderActive()) {
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Conditional rendering is not active.");
return;
}
MG_State::pGLContext->EndConditionalRender();
}
void GetQueryiv(GLenum target, GLenum pname, GLint* params) {
if (!params) {
return;
+5
View File
@@ -29,6 +29,11 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
void QueryCounter(GLuint id, GLenum target);
// Conditional rendering (GL 4.6 core 10.9). Implemented here rather than beside the drawing
// entry points because the predicate is a QUERY OBJECT's result, and the object registry -
// with the lock that guards it - lives in this file.
void BeginConditionalRender(GLuint id, GLenum mode);
void EndConditionalRender();
// Destroys every still-registered query object exactly as DeleteQueries would.
// GL requires queries to die with their context; called only from full library
// teardown (DestroyImpl), where no context survives on any thread, so the
+32
View File
@@ -368,6 +368,31 @@ namespace MobileGL {
return m_transformFeedbackGeometryCaptureDraws;
}
// Conditional rendering (GL 4.6 core 10.9). `discard` is the verdict already
// resolved from the query object at glBeginConditionalRender - the predicate is
// read ONCE there, not per command, because GL specifies the block against the
// result available at Begin and re-reading it would let a query that is still
// being written change the answer mid-block.
void BeginConditionalRender(GLuint queryId, GLenum mode, Bool discard) {
m_conditionalRenderActive = true;
m_conditionalRenderQuery = queryId;
m_conditionalRenderMode = mode;
m_conditionalRenderDiscards = discard;
}
void EndConditionalRender() {
m_conditionalRenderActive = false;
m_conditionalRenderQuery = 0;
m_conditionalRenderMode = GL_NONE;
m_conditionalRenderDiscards = false;
}
Bool IsConditionalRenderActive() const { return m_conditionalRenderActive; }
GLuint GetConditionalRenderQuery() const { return m_conditionalRenderQuery; }
// Whether the commands GL 4.6 core 10.9 makes conditional are being discarded
// right now. False whenever no block is open, so a caller needs no second test.
Bool ConditionalRenderDiscardsCommands() const {
return m_conditionalRenderActive && m_conditionalRenderDiscards;
}
// Transform feedback objects (ARB_transform_feedback2 / GL 4.0 core).
// The capture state above and the indexed GL_TRANSFORM_FEEDBACK_BUFFER
// binding points are object state, but the context keeps exactly one live
@@ -466,6 +491,13 @@ namespace MobileGL {
Uint64 m_transformFeedbackAccountedCaptureDraws = 0;
Uint64 m_transformFeedbackGeometryCaptureDraws = 0;
// Conditional rendering. Context state, not object state: GL 4.6 core 10.9 allows
// exactly one block open at a time and no object owns it.
Bool m_conditionalRenderActive = false;
Bool m_conditionalRenderDiscards = false;
GLuint m_conditionalRenderQuery = 0;
GLenum m_conditionalRenderMode = GL_NONE;
// Everything a transform feedback object owns while it is NOT the bound one.
struct TransformFeedbackObjectState {
struct SavedBufferBinding {
+125
View File
@@ -140,6 +140,29 @@ namespace {
void StubEndXfbPrimitivesQuery(MG_Backend::BackendQueryHandle) { ++g_stubXfbEndCount; }
// Stub backend occlusion queries. The host has no ES context, and BeginQuery refuses the
// occlusion targets outright when the backend advertises no hook - so a conditional-render
// test cannot get a legal predicate object without these. g_stubResultNs is the sample count
// the "driver" reports, which is the whole input to the predicate.
MG_Backend::BackendQueryHandle StubBeginOcclusionQuery() {
return reinterpret_cast<MG_Backend::BackendQueryHandle>(static_cast<uintptr_t>(0x54));
}
void StubEndOcclusionQuery(MG_Backend::BackendQueryHandle) {}
void InstallStubBackendOcclusionQueries() {
auto& backendGL = MG_Backend::gBackendFunctionsTable.GL;
backendGL.BeginOcclusionQuery = StubBeginOcclusionQuery;
backendGL.EndOcclusionQuery = StubEndOcclusionQuery;
backendGL.IsQueryResultAvailable = StubIsQueryResultAvailable;
backendGL.GetQueryResult64 = StubGetQueryResult64;
backendGL.DeleteBackendQuery = StubDeleteBackendQuery;
g_stubDeleteCount = 0;
g_stubResultAvailable = true;
g_stubResultObtainable = true;
g_stubResultNs = 0;
}
void InstallStubBackendXfbQueries() {
auto& backendGL = MG_Backend::gBackendFunctionsTable.GL;
backendGL.BeginXfbPrimitivesQuery = StubBeginXfbPrimitivesQuery;
@@ -677,6 +700,108 @@ TEST_F(QueryTest, PrimitivesGeneratedKeepsTheBackendResultUnderTheCpuPreference)
// unified truthy rule (set, non-empty, not "0", case-insensitive not "false").
// Running the binary under MOBILEGL_DISABLE_TIMERQUERY=1 therefore exercises
// the real end-to-end path rather than the struct field alone.
// KHR-GL43.compute_shader.conditional-dispatching and the conditional_render family.
// glBeginConditionalRender/glEndConditionalRender were bare stubs: every command inside a
// conditional block executed whatever the query said, so the block that should have been
// discarded ran and doubled the atomic counter the case reads back.
TEST_F(QueryTest, ConditionalRenderResolvesItsPredicateFromTheOcclusionQuery) {
ScopedBackendFunctionsOverride backendGuard;
InstallStubBackendOcclusionQueries();
GLuint ids[2] = {0, 0};
MG_Impl::GLImpl::GenQueries(2, ids);
ASSERT_NE(ids[0], 0u);
ASSERT_NE(ids[1], 0u);
// One span that saw samples and one that saw none, which is exactly the pair the
// conformance case builds out of a passing and a failing depth test.
g_stubResultNs = 1;
MG_Impl::GLImpl::BeginQuery(GL_ANY_SAMPLES_PASSED, ids[0]);
MG_Impl::GLImpl::EndQuery(GL_ANY_SAMPLES_PASSED);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLuint passedResult = 0xFFFFFFFFu;
MG_Impl::GLImpl::GetQueryObjectuiv(ids[0], GL_QUERY_RESULT, &passedResult);
ASSERT_EQ(passedResult, 1u);
g_stubResultNs = 0;
MG_Impl::GLImpl::BeginQuery(GL_ANY_SAMPLES_PASSED, ids[1]);
MG_Impl::GLImpl::EndQuery(GL_ANY_SAMPLES_PASSED);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// A block on the query that passed executes.
MG_Impl::GLImpl::BeginConditionalRender(ids[0], GL_QUERY_WAIT);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_TRUE(MG_State::pGLContext->IsConditionalRenderActive());
EXPECT_FALSE(MG_State::pGLContext->ConditionalRenderDiscardsCommands());
MG_Impl::GLImpl::EndConditionalRender();
EXPECT_FALSE(MG_State::pGLContext->IsConditionalRenderActive());
EXPECT_FALSE(MG_State::pGLContext->ConditionalRenderDiscardsCommands());
// A block on the query that did not passes nothing through.
MG_Impl::GLImpl::BeginConditionalRender(ids[1], GL_QUERY_WAIT);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_TRUE(MG_State::pGLContext->ConditionalRenderDiscardsCommands());
MG_Impl::GLImpl::EndConditionalRender();
// ...and the _INVERTED modes swap both verdicts.
MG_Impl::GLImpl::BeginConditionalRender(ids[0], GL_QUERY_WAIT_INVERTED);
EXPECT_TRUE(MG_State::pGLContext->ConditionalRenderDiscardsCommands());
MG_Impl::GLImpl::EndConditionalRender();
MG_Impl::GLImpl::BeginConditionalRender(ids[1], GL_QUERY_BY_REGION_NO_WAIT_INVERTED);
EXPECT_FALSE(MG_State::pGLContext->ConditionalRenderDiscardsCommands());
MG_Impl::GLImpl::EndConditionalRender();
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteQueries(2, ids);
}
TEST_F(QueryTest, ConditionalRenderRejectsTheErrorsTheSpecNames) {
ScopedBackendFunctionsOverride backendGuard;
InstallStubBackendOcclusionQueries();
GLuint ids[2] = {0, 0};
MG_Impl::GLImpl::GenQueries(2, ids);
g_stubResultNs = 1;
MG_Impl::GLImpl::BeginQuery(GL_ANY_SAMPLES_PASSED, ids[0]);
MG_Impl::GLImpl::EndQuery(GL_ANY_SAMPLES_PASSED);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// GL 4.6 core 10.9, one rule at a time.
MG_Impl::GLImpl::BeginConditionalRender(ids[0], GL_TIME_ELAPSED);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_ENUM);
EXPECT_FALSE(MG_State::pGLContext->IsConditionalRenderActive());
// A generated NAME is not yet a query object.
MG_Impl::GLImpl::BeginConditionalRender(ids[1], GL_QUERY_WAIT);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
MG_Impl::GLImpl::BeginConditionalRender(0, GL_QUERY_WAIT);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
// A query that is not an occlusion query cannot drive one.
GLuint timerId = 0;
MG_Impl::GLImpl::GenQueries(1, &timerId);
MG_Impl::GLImpl::BeginQuery(GL_TIME_ELAPSED, timerId);
MG_Impl::GLImpl::EndQuery(GL_TIME_ELAPSED);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BeginConditionalRender(timerId, GL_QUERY_WAIT);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// End without a block, and a nested Begin.
MG_Impl::GLImpl::EndConditionalRender();
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
MG_Impl::GLImpl::BeginConditionalRender(ids[0], GL_QUERY_WAIT);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BeginConditionalRender(ids[0], GL_QUERY_WAIT);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// The rejected nested Begin must not have disturbed the open block.
EXPECT_EQ(MG_State::pGLContext->GetConditionalRenderQuery(), ids[0]);
MG_Impl::GLImpl::EndConditionalRender();
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteQueries(2, ids);
MG_Impl::GLImpl::DeleteQueries(1, &timerId);
}
TEST_F(QueryTest, DisableTimerQueryFeatureMatchesEnvironment) {
const char* raw = std::getenv("MOBILEGL_DISABLE_TIMERQUERY");
Bool expected = false;