Commit 8305ebd4 by Tim Van Patten Committed by Commit Bot

Add tests to show bad waitSync/flush behavior

Add the following tests to show ANGLE's poor behavior with glWaitSync/glFlush leading to a vkQueueSubmit each time: FenceSyncTest::MultipleFenceDraw MultithreadingTestES3::MultithreadFenceDraw MultithreadingTestES3::MultithreadFenceTexImage Bug: angleproject:5306 Test: FenceSyncTest::MultipleFenceDraw Test: MultithreadingTestES3::MultithreadFenceDraw Test: MultithreadingTestES3, MultithreadFenceTexImage Change-Id: I16f653af028677e3c621c3b9a7fd5963fa03d656 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2519092Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Commit-Queue: Tim Van Patten <timvp@google.com>
parent 9a0284e8
......@@ -5,6 +5,7 @@
//
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
......@@ -25,11 +26,14 @@ class FenceNVTest : public ANGLETest
class FenceSyncTest : public ANGLETest
{
public:
static constexpr uint32_t kSize = 1024;
protected:
FenceSyncTest()
{
setWindowWidth(128);
setWindowHeight(128);
setWindowWidth(kSize);
setWindowHeight(kSize);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
......@@ -259,6 +263,78 @@ TEST_P(FenceSyncTest, BasicOperations)
}
}
// Test that multiple fences and draws can be issued
TEST_P(FenceSyncTest, MultipleFenceDraw)
{
constexpr int kNumIterations = 10;
constexpr int kNumDraws = 5;
// Create a texture/FBO to draw to
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture.get());
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
ASSERT_GL_NO_ERROR();
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
bool drawGreen = true;
for (int numIterations = 0; numIterations < kNumIterations; ++numIterations)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
ASSERT_GL_NO_ERROR();
for (int i = 0; i < kNumDraws; ++i)
{
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
ASSERT_GL_NO_ERROR();
// Force the fence to be created
glFlush();
drawGreen = !drawGreen;
GLuint program = 0;
if (drawGreen)
{
program = greenProgram.get();
}
else
{
program = redProgram.get();
}
drawQuad(program, std::string(essl1_shaders::PositionAttrib()), 0.0f);
ASSERT_GL_NO_ERROR();
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
EXPECT_GL_NO_ERROR();
glDeleteSync(sync);
ASSERT_GL_NO_ERROR();
}
// Blit to the default FBO
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
ASSERT_GL_NO_ERROR();
swapBuffers();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLColor color;
if (drawGreen)
{
color = GLColor::green;
}
else
{
color = GLColor::red;
}
EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, color);
}
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(FenceNVTest);
......
......@@ -19,11 +19,14 @@ namespace angle
class MultithreadingTest : public ANGLETest
{
public:
static constexpr uint32_t kSize = 512;
protected:
MultithreadingTest()
{
setWindowWidth(128);
setWindowHeight(128);
setWindowWidth(kSize);
setWindowHeight(kSize);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
......@@ -89,6 +92,54 @@ class MultithreadingTest : public ANGLETest
}
};
class MultithreadingTestES3 : public MultithreadingTest
{
public:
void textureThreadFunction(bool useDraw);
void mainThreadDraw(bool useDraw);
protected:
MultithreadingTestES3()
: mTexture2D(0), mExitThread(false), mMainThreadSyncObj(NULL), mSecondThreadSyncObj(NULL)
{
setWindowWidth(kSize);
setWindowHeight(kSize);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
GLuint create2DTexture()
{
GLuint texture2D;
glGenTextures(1, &texture2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
EXPECT_GL_NO_ERROR();
return texture2D;
}
void testSetUp() override { mTexture2D = create2DTexture(); }
void testTearDown() override
{
if (mTexture2D)
{
glDeleteTextures(1, &mTexture2D);
}
}
std::mutex mutex;
GLuint mTexture2D;
std::atomic<bool> mExitThread;
std::atomic<bool> mDrawGreen; // Toggle drawing green or red
std::atomic<GLsync> mMainThreadSyncObj;
std::atomic<GLsync> mSecondThreadSyncObj;
};
// Test that it's possible to make one context current on different threads
TEST_P(MultithreadingTest, MakeCurrentSingleContext)
{
......@@ -163,6 +214,8 @@ TEST_P(MultithreadingTest, MultiContextDraw)
{
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
ANGLE_SKIP_TEST_IF(isSwiftshader());
auto testBody = [](EGLSurface surface, size_t thread) {
constexpr size_t kIterationsPerThread = 32;
constexpr size_t kDrawsPerIteration = 500;
......@@ -360,12 +413,246 @@ TEST_P(MultithreadingTest, MultiCreateContext)
EXPECT_EGL_SUCCESS();
}
void MultithreadingTestES3::textureThreadFunction(bool useDraw)
{
EGLWindow *window = getEGLWindow();
EGLDisplay dpy = window->getDisplay();
EGLConfig config = window->getConfig();
EGLSurface surface = EGL_NO_SURFACE;
EGLContext ctx = EGL_NO_CONTEXT;
// Initialize the pbuffer and context
EGLint pbufferAttributes[] = {
EGL_WIDTH, kSize, EGL_HEIGHT, kSize, EGL_NONE, EGL_NONE,
};
surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
EXPECT_EGL_SUCCESS();
EXPECT_NE(EGL_NO_SURFACE, surface);
ctx = window->createContext(window->getContext());
EXPECT_NE(EGL_NO_CONTEXT, ctx);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
EXPECT_EGL_SUCCESS();
std::vector<GLColor> greenColor(kSize * kSize, GLColor::green);
std::vector<GLColor> redColor(kSize * kSize, GLColor::red);
ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
ASSERT_GL_NO_ERROR();
// Force the fence to be created
glFlush();
// Draw something
while (!mExitThread)
{
std::lock_guard<decltype(mutex)> lock(mutex);
if (mMainThreadSyncObj != nullptr)
{
glWaitSync(mMainThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
ASSERT_GL_NO_ERROR();
glDeleteSync(mSecondThreadSyncObj);
ASSERT_GL_NO_ERROR();
mMainThreadSyncObj = nullptr;
}
else
{
continue;
}
glBindTexture(GL_TEXTURE_2D, mTexture2D);
ASSERT_GL_NO_ERROR();
if (mDrawGreen)
{
if (useDraw)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
drawQuad(greenProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
}
else
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
greenColor.data());
}
ASSERT_GL_NO_ERROR();
}
else
{
if (useDraw)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
drawQuad(redProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
}
else
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
redColor.data());
}
ASSERT_GL_NO_ERROR();
}
ASSERT_EQ(mSecondThreadSyncObj.load(), nullptr);
mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
ASSERT_GL_NO_ERROR();
// Force the fence to be created
glFlush();
mDrawGreen = !mDrawGreen;
}
// Clean up
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
eglDestroySurface(dpy, surface);
eglDestroyContext(dpy, ctx);
}
// Test fence sync with multiple threads drawing
void MultithreadingTestES3::mainThreadDraw(bool useDraw)
{
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
EGLWindow *window = getEGLWindow();
EGLDisplay dpy = window->getDisplay();
EGLContext ctx = window->getContext();
EGLSurface surface = window->getSurface();
// Use odd numbers so we bounce between red and green in the final image
constexpr int kNumIterations = 5;
constexpr int kNumDraws = 5;
std::thread textureThread(&MultithreadingTestES3::textureThreadFunction, this, true);
ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
for (int iterations = 0; iterations < kNumIterations; ++iterations)
{
for (int draws = 0; draws < kNumDraws;)
{
std::lock_guard<decltype(mutex)> lock(mutex);
if (mSecondThreadSyncObj != nullptr)
{
glWaitSync(mSecondThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
ASSERT_GL_NO_ERROR();
glDeleteSync(mSecondThreadSyncObj);
ASSERT_GL_NO_ERROR();
mSecondThreadSyncObj = nullptr;
}
else
{
continue;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUseProgram(texProgram);
drawQuad(texProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
ASSERT_EQ(mMainThreadSyncObj.load(), nullptr);
mMainThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
ASSERT_GL_NO_ERROR();
// Force the fence to be created
glFlush();
++draws;
}
ASSERT_GL_NO_ERROR();
swapBuffers();
}
mExitThread = true;
textureThread.join();
ASSERT_GL_NO_ERROR();
GLColor color;
if (mDrawGreen)
{
color = GLColor::green;
}
else
{
color = GLColor::red;
}
EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, color);
// Re-make current the test window's context for teardown.
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
EXPECT_EGL_SUCCESS();
}
// Test that glFenceSync/glWaitSync works correctly with multithreading.
// Main thread: Samples from the shared texture to draw to the default FBO.
// Secondary (Texture) thread: Draws to the shared texture, which the Main thread samples from.
// The overall execution flow is:
// Main Thread:
// 1. Wait for the mSecondThreadSyncObj fence object to be created.
// - This fence object is used by synchronize access to the shared texture by indicating that the
// Secondary thread's draws to the texture have all completed and it's now safe to sample from
// it.
// 2. Once the fence is created, add a glWaitSync(mSecondThreadSyncObj) to the command stream and
// then delete it.
// 3. Draw, sampling from the shared texture.
// 4. Create a new mMainThreadSyncObj.
// - This fence object is used to synchronize access to the shared texture by indicating that the
// Main thread's draws are no longer sampling from the texture, so it's now safe for the
// Secondary thread to draw to it again with a new color.
// Secondary (Texture) Thread:
// 1. Wait for the mMainThreadSyncObj fence object to be created.
// 2. Once the fence is created, add a glWaitSync(mMainThreadSyncObj) to the command stream and then
// delete it.
// 3. Draw/Fill the texture.
// 4. Create a new mSecondThreadSyncObj.
//
// These threads loop for the specified number of iterations, drawing/sampling the shared texture
// with the necessary glFlush()s and occasional eglSwapBuffers() to mimic a real multithreaded GLES
// application.
TEST_P(MultithreadingTestES3, MultithreadFenceDraw)
{
// Have the secondary thread use glDrawArrays()
mainThreadDraw(true);
}
// Same as MultithreadFenceDraw, but with the secondary thread using glTexImage2D rather than
// glDrawArrays.
TEST_P(MultithreadingTestES3, MultithreadFenceTexImage)
{
// Have the secondary thread use glTexImage2D()
mainThreadDraw(false);
}
// TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads
ANGLE_INSTANTIATE_TEST(MultithreadingTest,
WithNoVirtualContexts(ES2_OPENGL()),
WithNoVirtualContexts(ES3_OPENGL()),
WithNoVirtualContexts(ES2_OPENGLES()),
WithNoVirtualContexts(ES3_OPENGLES()));
WithNoVirtualContexts(ES3_OPENGLES()),
WithNoVirtualContexts(ES3_VULKAN()),
WithNoVirtualContexts(ES3_VULKAN_SWIFTSHADER()));
ANGLE_INSTANTIATE_TEST(MultithreadingTestES3,
WithNoVirtualContexts(ES3_OPENGL()),
WithNoVirtualContexts(ES3_OPENGLES()),
WithNoVirtualContexts(ES3_VULKAN()),
WithNoVirtualContexts(ES3_VULKAN_SWIFTSHADER()));
} // namespace angle
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