Commit 292f005f by Ian Ewell Committed by Commit Bot

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/325811Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: Ian Ewell <ewell@google.com>
parent a78d12c5
......@@ -316,6 +316,9 @@ void Context::makeCurrent(egl::Surface *surface)
}
mFramebufferMap[0] = newDefault;
}
// Notify the renderer of a context switch
mRenderer->onMakeCurrent(getData());
}
void Context::releaseSurface()
......
......@@ -91,9 +91,13 @@ class Renderer : public ImplFactory
virtual void syncState(const gl::State &state, const gl::State::DirtyBits &dirtyBits) = 0;
// Disjoint timer queries
virtual GLint getGPUDisjoint() = 0;
virtual GLint64 getTimestamp() = 0;
// Context switching
virtual void onMakeCurrent(const gl::Data &data) = 0;
// Renderer capabilities
const gl::Caps &getRendererCaps() const;
const gl::TextureCapsMap &getRendererTextureCaps() const;
......
......@@ -686,6 +686,10 @@ GLint64 RendererD3D::getTimestamp()
return 0;
}
void RendererD3D::onMakeCurrent(const gl::Data &data)
{
}
void RendererD3D::initializeDebugAnnotator()
{
createAnnotator();
......
......@@ -246,6 +246,8 @@ class RendererD3D : public Renderer, public BufferFactoryD3D
GLint getGPUDisjoint() override;
GLint64 getTimestamp() override;
void onMakeCurrent(const gl::Data &data) override;
// In D3D11, faster than calling setTexture a jillion times
virtual gl::Error clearTextures(gl::SamplerType samplerType, size_t rangeStart, size_t rangeEnd) = 0;
......
......@@ -68,7 +68,8 @@ QueryGL::~QueryGL()
gl::Error QueryGL::begin()
{
mResultSum = 0;
return gl::Error(GL_NO_ERROR);
mStateManager->onBeginQuery(this);
return resume();
}
gl::Error QueryGL::end()
......
......@@ -429,4 +429,10 @@ GLint64 RendererGL::getTimestamp()
mFunctions->getInteger64v(GL_TIMESTAMP, &result);
return result;
}
void RendererGL::onMakeCurrent(const gl::Data &data)
{
// Queries need to be paused/resumed on context switches
mStateManager->onMakeCurrent(data);
}
}
......@@ -107,6 +107,8 @@ class RendererGL : public Renderer
GLint getGPUDisjoint() override;
GLint64 getTimestamp() override;
void onMakeCurrent(const gl::Data &data) override;
const gl::Version &getMaxSupportedESVersion() const;
const FunctionsGL *getFunctions() const { return mFunctions; }
StateManagerGL *getStateManager() const { return mStateManager; }
......
......@@ -47,7 +47,7 @@ StateManagerGL::StateManagerGL(const FunctionsGL *functions, const gl::Caps &ren
mTransformFeedback(0),
mQueries(),
mPrevDrawTransformFeedback(nullptr),
mPrevDrawQueries(),
mCurrentQueries(),
mPrevDrawContext(0),
mUnpackAlignment(4),
mUnpackRowLength(0),
......@@ -577,9 +577,14 @@ void StateManagerGL::endQuery(GLenum type, GLuint query)
mFunctions->endQuery(type);
}
void StateManagerGL::onBeginQuery(QueryGL *query)
{
mCurrentQueries.insert(query);
}
void StateManagerGL::onDeleteQueryObject(QueryGL *query)
{
mPrevDrawQueries.erase(query);
mCurrentQueries.erase(query);
}
gl::Error StateManagerGL::setDrawArraysState(const gl::Data &data,
......@@ -633,7 +638,7 @@ gl::Error StateManagerGL::setDrawElementsState(const gl::Data &data,
return setGenericDrawState(data);
}
gl::Error StateManagerGL::setGenericDrawState(const gl::Data &data)
gl::Error StateManagerGL::onMakeCurrent(const gl::Data &data)
{
const gl::State &state = *data.state;
......@@ -645,16 +650,35 @@ gl::Error StateManagerGL::setGenericDrawState(const gl::Data &data)
mPrevDrawTransformFeedback->syncPausedState(true);
}
for (QueryGL *prevQuery : mPrevDrawQueries)
for (QueryGL *prevQuery : mCurrentQueries)
{
prevQuery->pause();
}
}
mCurrentQueries.clear();
mPrevDrawTransformFeedback = nullptr;
mPrevDrawQueries.clear();
mPrevDrawContext = data.context;
// Set the current query state
for (GLenum queryType : QueryTypes)
{
gl::Query *query = state.getActiveQuery(queryType);
if (query != nullptr)
{
QueryGL *queryGL = GetImplAs<QueryGL>(query);
queryGL->resume();
mCurrentQueries.insert(queryGL);
}
}
return gl::Error(GL_NO_ERROR);
}
gl::Error StateManagerGL::setGenericDrawState(const gl::Data &data)
{
const gl::State &state = *data.state;
// Sync the current program state
const gl::Program *program = state.getProgram();
const ProgramGL *programGL = GetImplAs<ProgramGL>(program);
......@@ -751,19 +775,6 @@ gl::Error StateManagerGL::setGenericDrawState(const gl::Data &data)
mPrevDrawTransformFeedback = nullptr;
}
// Set the current query state
for (GLenum queryType : QueryTypes)
{
gl::Query *query = state.getActiveQuery(queryType);
if (query != nullptr)
{
QueryGL *queryGL = GetImplAs<QueryGL>(query);
queryGL->resume();
mPrevDrawQueries.insert(queryGL);
}
}
return gl::Error(GL_NO_ERROR);
}
......
......@@ -59,6 +59,7 @@ class StateManagerGL final : angle::NonCopyable
void bindTransformFeedback(GLenum type, GLuint transformFeedback);
void beginQuery(GLenum type, GLuint query);
void endQuery(GLenum type, GLuint query);
void onBeginQuery(QueryGL *query);
void setAttributeCurrentData(size_t index, const gl::VertexAttribCurrentValueData &data);
......@@ -135,6 +136,8 @@ class StateManagerGL final : angle::NonCopyable
GLsizei instanceCount,
const GLvoid **outIndices);
gl::Error onMakeCurrent(const gl::Data &data);
void syncState(const gl::State &state, const gl::State::DirtyBits &glDirtyBits);
private:
......@@ -170,7 +173,7 @@ class StateManagerGL final : angle::NonCopyable
std::map<GLenum, GLuint> mQueries;
TransformFeedbackGL *mPrevDrawTransformFeedback;
std::set<QueryGL *> mPrevDrawQueries;
std::set<QueryGL *> mCurrentQueries;
uintptr_t mPrevDrawContext;
GLint mUnpackAlignment;
......
......@@ -164,6 +164,75 @@ TEST_P(TimerQueriesTest, TimeElapsed)
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 @@ TEST_P(TimerQueriesTest, TimeElapsedValidationTest)
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)
{
......
......@@ -463,6 +463,15 @@ bool ANGLETest::isD3DSM3() const
return isD3D9() || isD3D11_FL93();
}
bool ANGLETest::isOSX() const
{
#ifdef __APPLE__
return true;
#else
return false;
#endif
}
EGLint ANGLETest::getPlatformRenderer() const
{
assert(mEGLWindow);
......
......@@ -142,6 +142,7 @@ class ANGLETest : public ::testing::TestWithParam<angle::PlatformParameters>
bool isD3D9() const;
// Is D3D9 or SM9_3 renderer.
bool isD3DSM3() const;
bool isOSX() const;
EGLint getPlatformRenderer() const;
void ignoreD3D11SDKLayersWarnings();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment