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)
 {