Fix context virtualization for timer queries
In the current implementation of query virtualization, queries are
paused/resumed during draw calls. Timer queries however are affected by
every OpenGL call, so virtualization and context switches for timer
queries must happen every time there is a context switch. Thus the logic
for context-switching queries was moved to a new function in the GL state
manager that is called everytime a makeCurrent call on the context is
made. Since queries may be made after a context is made current, the state
manager needs to keep track of any new queries that are started in a
context, so an additional delegate function was added to the state manager
that is called every time a glBeginQuery() call is made that adds the
query object to a set. All the queries in that set are paused when a
context switch is made and the queries in the new context are then loaded
and resumed.
BUG=angleproject:1307
Change-Id: I4e596d83739274cb2e14152a39e86e0e51b0f95c
Reviewed-on: https://chromium-review.googlesource.com/325811
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Ian Ewell <ewell@google.com>
diff --git a/src/tests/gl_tests/TimerQueriesTest.cpp b/src/tests/gl_tests/TimerQueriesTest.cpp
index 3c25790..e2ecd82 100644
--- a/src/tests/gl_tests/TimerQueriesTest.cpp
+++ b/src/tests/gl_tests/TimerQueriesTest.cpp
@@ -164,6 +164,75 @@
EXPECT_LT(result1, result2);
}
+// Tests time elapsed for a non draw call (texture upload)
+TEST_P(TimerQueriesTest, TimeElapsedTextureTest)
+{
+ // OSX drivers don't seem to properly time non-draw calls so we skip the test on Mac
+ if (isOSX())
+ {
+ std::cout << "Test skipped on OSX" << std::endl;
+ return;
+ }
+
+ if (!extensionEnabled("GL_EXT_disjoint_timer_query"))
+ {
+ std::cout << "Test skipped because GL_EXT_disjoint_timer_query is not available."
+ << std::endl;
+ return;
+ }
+
+ GLint queryTimeElapsedBits = 0;
+ glGetQueryivEXT(GL_TIME_ELAPSED_EXT, GL_QUERY_COUNTER_BITS_EXT, &queryTimeElapsedBits);
+ ASSERT_GL_NO_ERROR();
+
+ std::cout << "Time elapsed counter bits: " << queryTimeElapsedBits << std::endl;
+
+ // Skip test if the number of bits is 0
+ if (queryTimeElapsedBits == 0)
+ {
+ std::cout << "Test skipped because of 0 counter bits" << std::endl;
+ return;
+ }
+
+ GLubyte pixels[] = {0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0};
+
+ // Query and texture initialization
+ GLuint texture;
+ GLuint query = 0;
+ glGenQueriesEXT(1, &query);
+ glGenTextures(1, &texture);
+
+ // Upload a texture inside the query
+ glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ glFinish();
+ glEndQueryEXT(GL_TIME_ELAPSED_EXT);
+ ASSERT_GL_NO_ERROR();
+
+ int timeout = 200000;
+ GLuint ready = GL_FALSE;
+ while (ready == GL_FALSE && timeout > 0)
+ {
+ angle::Sleep(0);
+ glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_AVAILABLE_EXT, &ready);
+ timeout--;
+ }
+ ASSERT_LT(0, timeout) << "Query result available timed out" << std::endl;
+
+ GLuint64 result = 0;
+ glGetQueryObjectui64vEXT(query, GL_QUERY_RESULT_EXT, &result);
+ ASSERT_GL_NO_ERROR();
+
+ glDeleteTextures(1, &texture);
+ glDeleteQueriesEXT(1, &query);
+
+ std::cout << "Elapsed time: " << result << std::endl;
+ EXPECT_LT(0ul, result);
+}
+
// Tests validation of query functions with respect to elapsed time query
TEST_P(TimerQueriesTest, TimeElapsedValidationTest)
{
@@ -216,6 +285,181 @@
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
+// Tests timer queries operating under multiple EGL contexts with mid-query switching
+TEST_P(TimerQueriesTest, TimeElapsedMulticontextTest)
+{
+ if (!extensionEnabled("GL_EXT_disjoint_timer_query"))
+ {
+ std::cout << "Test skipped because GL_EXT_disjoint_timer_query is not available."
+ << std::endl;
+ return;
+ }
+
+ GLint queryTimeElapsedBits = 0;
+ glGetQueryivEXT(GL_TIME_ELAPSED_EXT, GL_QUERY_COUNTER_BITS_EXT, &queryTimeElapsedBits);
+ ASSERT_GL_NO_ERROR();
+
+ std::cout << "Time elapsed counter bits: " << queryTimeElapsedBits << std::endl;
+
+ // Skip test if the number of bits is 0
+ if (queryTimeElapsedBits == 0)
+ {
+ std::cout << "Test skipped because of 0 counter bits" << std::endl;
+ return;
+ }
+
+ // D3D multicontext isn't implemented yet
+ if (GetParam() == ES3_D3D11() || GetParam() == ES2_D3D11())
+ {
+ std::cout
+ << "Test skipped because the D3D backends cannot support simultaneous timer queries yet"
+ << std::endl;
+ return;
+ }
+
+ EGLint contextAttributes[] = {
+ EGL_CONTEXT_MAJOR_VERSION_KHR,
+ GetParam().majorVersion,
+ EGL_CONTEXT_MINOR_VERSION_KHR,
+ GetParam().minorVersion,
+ EGL_NONE,
+ };
+
+ EGLWindow *window = getEGLWindow();
+
+ EGLDisplay display = window->getDisplay();
+ EGLConfig config = window->getConfig();
+ EGLSurface surface = window->getSurface();
+
+ struct ContextInfo
+ {
+ EGLContext context;
+ GLuint program;
+ GLuint query;
+ EGLDisplay display;
+
+ ContextInfo() : context(EGL_NO_CONTEXT), program(0), query(0), display(EGL_NO_DISPLAY) {}
+
+ ~ContextInfo()
+ {
+ if (context != EGL_NO_CONTEXT && display != EGL_NO_DISPLAY)
+ {
+ eglDestroyContext(display, context);
+ }
+ }
+ };
+ ContextInfo contexts[2];
+
+ // Shaders
+ const std::string cheapVS =
+ "attribute highp vec4 position; void main(void)\n"
+ "{\n"
+ " gl_Position = position;\n"
+ "}\n";
+
+ const std::string cheapPS =
+ "precision highp float; void main(void)\n"
+ "{\n"
+ " gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+ "}\n";
+
+ const std::string costlyVS =
+ "attribute highp vec4 position; varying highp vec4 testPos; void main(void)\n"
+ "{\n"
+ " testPos = position;\n"
+ " gl_Position = position;\n"
+ "}\n";
+
+ const std::string costlyPS =
+ "precision highp float; varying highp vec4 testPos; void main(void)\n"
+ "{\n"
+ " vec4 test = testPos;\n"
+ " for (int i = 0; i < 500; i++)\n"
+ " {\n"
+ " test = sqrt(test);\n"
+ " }\n"
+ " gl_FragColor = test;\n"
+ "}\n";
+
+ // Setup the first context with a cheap shader
+ contexts[0].context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes);
+ contexts[0].display = display;
+ ASSERT_NE(contexts[0].context, EGL_NO_CONTEXT);
+ eglMakeCurrent(display, surface, surface, contexts[0].context);
+ contexts[0].program = CompileProgram(cheapVS, cheapPS);
+ glGenQueriesEXT(1, &contexts[0].query);
+ ASSERT_GL_NO_ERROR();
+
+ // Setup the second context with an expensive shader
+ contexts[1].context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes);
+ contexts[1].display = display;
+ ASSERT_NE(contexts[1].context, EGL_NO_CONTEXT);
+ eglMakeCurrent(display, surface, surface, contexts[1].context);
+ contexts[1].program = CompileProgram(costlyVS, costlyPS);
+ glGenQueriesEXT(1, &contexts[1].query);
+ ASSERT_GL_NO_ERROR();
+
+ // Start the query and draw a quad on the first context without ending the query
+ eglMakeCurrent(display, surface, surface, contexts[0].context);
+ glBeginQueryEXT(GL_TIME_ELAPSED_EXT, contexts[0].query);
+ drawQuad(contexts[0].program, "position", 0.8f);
+ ASSERT_GL_NO_ERROR();
+
+ // Switch contexts, draw the expensive quad and end its query
+ eglMakeCurrent(display, surface, surface, contexts[1].context);
+ glBeginQueryEXT(GL_TIME_ELAPSED_EXT, contexts[1].query);
+ drawQuad(contexts[1].program, "position", 0.8f);
+ glEndQueryEXT(GL_TIME_ELAPSED_EXT);
+ ASSERT_GL_NO_ERROR();
+
+ // Go back to the first context, end its query, and get the result
+ eglMakeCurrent(display, surface, surface, contexts[0].context);
+ glEndQueryEXT(GL_TIME_ELAPSED_EXT);
+ int timeout = 20000;
+ GLuint ready = GL_FALSE;
+ while (ready == GL_FALSE && timeout > 0)
+ {
+ angle::Sleep(0);
+ glGetQueryObjectuivEXT(contexts[0].query, GL_QUERY_RESULT_AVAILABLE_EXT, &ready);
+ timeout--;
+ }
+ ASSERT_LT(0, timeout) << "Query result available timed out" << std::endl;
+
+ GLuint64 result1 = 0;
+ GLuint64 result2 = 0;
+ glGetQueryObjectui64vEXT(contexts[0].query, GL_QUERY_RESULT_EXT, &result1);
+ glDeleteQueriesEXT(1, &contexts[0].query);
+ glDeleteProgram(contexts[0].program);
+ ASSERT_GL_NO_ERROR();
+
+ // Get the 2nd contexts results
+ eglMakeCurrent(display, surface, surface, contexts[1].context);
+ timeout = 20000;
+ ready = GL_FALSE;
+ while (ready == GL_FALSE && timeout > 0)
+ {
+ angle::Sleep(0);
+ glGetQueryObjectuivEXT(contexts[1].query, GL_QUERY_RESULT_AVAILABLE_EXT, &ready);
+ timeout--;
+ }
+ ASSERT_LT(0, timeout) << "Query result available timed out" << std::endl;
+ glGetQueryObjectui64vEXT(contexts[1].query, GL_QUERY_RESULT_EXT, &result2);
+ glDeleteQueriesEXT(1, &contexts[1].query);
+ glDeleteProgram(contexts[1].program);
+ ASSERT_GL_NO_ERROR();
+
+ // Switch back to main context
+ eglMakeCurrent(display, surface, surface, window->getContext());
+
+ // Compare the results. The cheap quad should be smaller than the expensive one if
+ // virtualization is working correctly
+ std::cout << "Elapsed time: " << result1 << " cheap quad" << std::endl;
+ std::cout << "Elapsed time: " << result2 << " costly quad" << std::endl;
+ EXPECT_LT(0ul, result1);
+ EXPECT_LT(0ul, result2);
+ EXPECT_LT(result1, result2);
+}
+
// Tests GPU timestamp functionality
TEST_P(TimerQueriesTest, Timestamp)
{