Vulkan: Perform CPU wait in clientWait outside the global lock

Leverage UnlockedTailCall and move the CPU side wait
during a clientWait outside of the global mutex lock.

Bug: angleproject:8340
Tests: FenceSyncTest.BasicOperations*
Tests: EGLSyncTest.EglClientWaitSync*
Change-Id: I8c05e62e74cc64d38bf8797d28faaf49135e71fc
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4851649
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: mohan maiya <m.maiya@samsung.com>
Reviewed-by: Charlie Lao <cclao@google.com>
This commit is contained in:
Mohan Maiya
2023-09-11 12:25:28 -07:00
committed by Angle LUCI CQ
parent b185c3eada
commit ce263437ca
11 changed files with 309 additions and 82 deletions

View File

@@ -6,7 +6,7 @@
"scripts/entry_point_packed_gl_enums.json":
"1c6b036918aabb9822a638fbf33f87f4",
"scripts/generate_entry_points.py":
"02bccd68542886dd17a751f98ad7d3eb",
"64ef644064c7fcbbf89458dc53ca5c94",
"scripts/gl_angle_ext.xml":
"3d6d9529c6d0da797652c366ddaa9087",
"scripts/registry_xml.py":
@@ -130,11 +130,11 @@
"src/libGLESv2/entry_points_cl_autogen.h":
"dde2f94c3004874a7da995dae69da811",
"src/libGLESv2/entry_points_egl_autogen.cpp":
"ec731a77790f0a557702b395d03af432",
"66320459d4a7d1df208cf9fc15779b80",
"src/libGLESv2/entry_points_egl_autogen.h":
"3bc7a8df9deadd7cfd615d0cfad0c6a8",
"src/libGLESv2/entry_points_egl_ext_autogen.cpp":
"7d86e5a581ef3062202552542afe6755",
"23f30e2c3e1cf0196e838cfecd8ac279",
"src/libGLESv2/entry_points_egl_ext_autogen.h":
"2d005f4cb16dcdd61e08cfec97a12f86",
"src/libGLESv2/entry_points_gl_1_autogen.cpp":
@@ -162,7 +162,7 @@
"src/libGLESv2/entry_points_gles_2_0_autogen.h":
"691c60c2dfed9beca68aa1f32aa2c71b",
"src/libGLESv2/entry_points_gles_3_0_autogen.cpp":
"3acd48e02ac5bb362c031b6f0b2c4b8b",
"5d44e91d02b54ef46169e31f7631aad5",
"src/libGLESv2/entry_points_gles_3_0_autogen.h":
"4ac2582759cdc6a30f78f83ab684d555",
"src/libGLESv2/entry_points_gles_3_1_autogen.cpp":

View File

@@ -416,7 +416,7 @@ TEMPLATE_GLES_ENTRY_POINT_WITH_RETURN = """\
{constext_lost_error_generator}
returnValue = GetDefaultReturnValue<angle::EntryPoint::GL{name}, {return_type}>();
}}
ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
{epilog}
return returnValue;
}}
"""
@@ -3068,6 +3068,9 @@ def get_unlocked_tail_call(api, cmd_name):
# - eglSwapBuffers, eglSwapBuffersWithDamageKHR and
# eglSwapBuffersWithFrameTokenANGLE -> May throttle the CPU in tail call
#
# - eglClientWaitSyncKHR, eglClientWaitSync, glClientWaitSync -> May wait on
# fence in tail call
#
if cmd_name in [
'eglDestroySurface', 'eglMakeCurrent', 'eglReleaseThread', 'eglCreateWindowSurface',
'eglCreatePlatformWindowSurface', 'eglCreatePlatformWindowSurfaceEXT',
@@ -3076,6 +3079,9 @@ def get_unlocked_tail_call(api, cmd_name):
]:
return 'egl::Display::GetCurrentThreadUnlockedTailCall()->run(nullptr);'
if cmd_name in ['eglClientWaitSyncKHR', 'eglClientWaitSync', 'glClientWaitSync']:
return 'egl::Display::GetCurrentThreadUnlockedTailCall()->run(&returnValue);'
# Otherwise assert that no tail calls where generated
return 'ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());'

View File

@@ -68,6 +68,64 @@ VkResult SyncWaitFd(int fd, uint64_t timeoutNs, VkResult timeoutResult = VK_TIME
#endif
}
// Map VkResult to GLenum
void MapVkResultToGlenum(VkResult vkResult, angle::Result angleResult, void *outResult)
{
GLenum *glEnumOut = static_cast<GLenum *>(outResult);
ASSERT(glEnumOut);
if (angleResult != angle::Result::Continue)
{
*glEnumOut = GL_WAIT_FAILED;
return;
}
switch (vkResult)
{
case VK_EVENT_SET:
*glEnumOut = GL_ALREADY_SIGNALED;
break;
case VK_SUCCESS:
*glEnumOut = GL_CONDITION_SATISFIED;
break;
case VK_TIMEOUT:
*glEnumOut = GL_TIMEOUT_EXPIRED;
break;
default:
*glEnumOut = GL_WAIT_FAILED;
break;
}
}
// Map VkResult to EGLint
void MapVkResultToEglint(VkResult result, angle::Result angleResult, void *outResult)
{
EGLint *eglIntOut = static_cast<EGLint *>(outResult);
ASSERT(eglIntOut);
if (angleResult != angle::Result::Continue)
{
*eglIntOut = EGL_FALSE;
return;
}
switch (result)
{
case VK_EVENT_SET:
// fall through. EGL doesn't differentiate between event being already set, or set
// before timeout.
case VK_SUCCESS:
*eglIntOut = EGL_CONDITION_SATISFIED_KHR;
break;
case VK_TIMEOUT:
*eglIntOut = EGL_TIMEOUT_EXPIRED_KHR;
break;
default:
*eglIntOut = EGL_FALSE;
break;
}
}
} // anonymous namespace
namespace rx
@@ -86,27 +144,25 @@ angle::Result SyncHelper::initialize(ContextVk *contextVk, bool isEGLSyncObject)
return contextVk->onSyncObjectInit(this, isEGLSyncObject);
}
angle::Result SyncHelper::clientWait(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *outResult)
angle::Result SyncHelper::prepareForClientWait(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *resultOut)
{
RendererVk *renderer = context->getRenderer();
// If the event is already set, don't wait
bool alreadySignaled = false;
ANGLE_TRY(getStatus(context, contextVk, &alreadySignaled));
if (alreadySignaled)
{
*outResult = VK_EVENT_SET;
*resultOut = VK_EVENT_SET;
return angle::Result::Continue;
}
// If timeout is zero, there's no need to wait, so return timeout already.
if (timeout == 0)
{
*outResult = VK_TIMEOUT;
*resultOut = VK_TIMEOUT;
return angle::Result::Continue;
}
@@ -116,19 +172,74 @@ angle::Result SyncHelper::clientWait(Context *context,
ANGLE_TRY(contextVk->flushCommandsAndEndRenderPassIfDeferredSyncInit(
RenderPassClosureReason::SyncObjectClientWait));
}
// Submit commands if it was deferred on the context that issued the sync object
ANGLE_TRY(submitSyncIfDeferred(contextVk, RenderPassClosureReason::SyncObjectClientWait));
VkResult status = VK_SUCCESS;
ANGLE_TRY(renderer->waitForResourceUseToFinishWithUserTimeout(context, mUse, timeout, &status));
*resultOut = VK_INCOMPLETE;
return angle::Result::Continue;
}
// Check for errors, but don't consider timeout as such.
if (status != VK_TIMEOUT)
angle::Result SyncHelper::clientWait(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *resultOut)
{
ANGLE_TRACE_EVENT0("gpu.angle", "SyncHelper clientWait");
ANGLE_TRY(prepareForClientWait(context, contextVk, flushCommands, timeout, resultOut));
if (*resultOut == VK_INCOMPLETE)
{
ANGLE_VK_TRY(context, status);
ANGLE_TRY(contextVk->getRenderer()->waitForResourceUseToFinishWithUserTimeout(
context, mUse, timeout, resultOut));
// Check for errors, but don't consider timeout as such.
if (*resultOut != VK_TIMEOUT)
{
ANGLE_VK_TRY(context, *resultOut);
}
}
return angle::Result::Continue;
}
angle::Result SyncHelper::clientWaitUnlocked(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
void *resultOut,
MapVkResultToApiType mappingFunction)
{
ANGLE_TRACE_EVENT0("gpu.angle", "SyncHelper clientWaitUnlocked");
VkResult status = VK_INCOMPLETE;
ANGLE_TRY(prepareForClientWait(context, contextVk, flushCommands, timeout, &status));
if (status == VK_INCOMPLETE)
{
RendererVk *renderer = context->getRenderer();
// If we need to perform a CPU wait don't set the resultOut parameter passed into the
// method, instead set the parameter passed into the unlocked tail call.
auto clientWaitUnlocked = [renderer, context, mappingFunction, use = mUse,
timeout](void *resultOut) {
ANGLE_TRACE_EVENT0("gpu.angle", "UnlockedTailCall clientWait");
ASSERT(resultOut);
VkResult status = VK_INCOMPLETE;
angle::Result angleResult =
renderer->waitForResourceUseToFinishWithUserTimeout(context, use, timeout, &status);
mappingFunction(status, angleResult, resultOut);
};
egl::Display::GetCurrentThreadUnlockedTailCall()->add(clientWaitUnlocked);
return angle::Result::Continue;
}
else
{
mappingFunction(status, angle::Result::Continue, resultOut);
}
*outResult = status;
return angle::Result::Continue;
}
@@ -343,7 +454,7 @@ angle::Result SyncHelperNativeFence::clientWait(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *outResult)
VkResult *resultOut)
{
RendererVk *renderer = context->getRenderer();
@@ -352,14 +463,14 @@ angle::Result SyncHelperNativeFence::clientWait(Context *context,
ANGLE_TRY(getStatus(context, contextVk, &alreadySignaled));
if (alreadySignaled)
{
*outResult = VK_SUCCESS;
*resultOut = VK_SUCCESS;
return angle::Result::Continue;
}
// If timeout is zero, there's no need to wait, so return timeout already.
if (timeout == 0)
{
*outResult = VK_TIMEOUT;
*resultOut = VK_TIMEOUT;
return angle::Result::Continue;
}
@@ -369,16 +480,31 @@ angle::Result SyncHelperNativeFence::clientWait(Context *context,
contextVk->flushImpl(nullptr, nullptr, RenderPassClosureReason::SyncObjectClientWait));
}
VkResult status = mExternalFence->wait(renderer->getDevice(), timeout);
if (status != VK_TIMEOUT)
*resultOut = mExternalFence->wait(renderer->getDevice(), timeout);
if (*resultOut != VK_TIMEOUT)
{
ANGLE_VK_TRY(contextVk, status);
ANGLE_VK_TRY(contextVk, *resultOut);
}
*outResult = status;
return angle::Result::Continue;
}
angle::Result SyncHelperNativeFence::clientWaitUnlocked(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
void *resultOut,
MapVkResultToApiType mappingFunction)
{
// Unlocked clientWait is not supported for SyncHelperNativeFence, yet.
// For now call into clientWait(...)
VkResult status = VK_INCOMPLETE;
angle::Result angleResult = clientWait(context, contextVk, flushCommands, timeout, &status);
mappingFunction(status, angleResult, resultOut);
return angleResult;
}
angle::Result SyncHelperNativeFence::serverWait(ContextVk *contextVk)
{
RendererVk *renderer = contextVk->getRenderer();
@@ -466,30 +592,10 @@ angle::Result SyncVk::clientWait(const gl::Context *context,
ASSERT((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) == 0);
bool flush = (flags & GL_SYNC_FLUSH_COMMANDS_BIT) != 0;
VkResult result;
ANGLE_TRY(mSyncHelper.clientWait(contextVk, contextVk, flush, static_cast<uint64_t>(timeout),
&result));
switch (result)
{
case VK_EVENT_SET:
*outResult = GL_ALREADY_SIGNALED;
return angle::Result::Continue;
case VK_SUCCESS:
*outResult = GL_CONDITION_SATISFIED;
return angle::Result::Continue;
case VK_TIMEOUT:
*outResult = GL_TIMEOUT_EXPIRED;
return angle::Result::Incomplete;
default:
UNREACHABLE();
*outResult = GL_WAIT_FAILED;
return angle::Result::Stop;
}
return mSyncHelper.clientWaitUnlocked(contextVk, contextVk, flush,
static_cast<uint64_t>(timeout), outResult,
MapVkResultToGlenum);
}
angle::Result SyncVk::serverWait(const gl::Context *context, GLbitfield flags, GLuint64 timeout)
@@ -569,33 +675,16 @@ egl::Error EGLSyncVk::clientWait(const egl::Display *display,
ASSERT((flags & ~EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) == 0);
bool flush = (flags & EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) != 0;
VkResult result;
ContextVk *contextVk = context ? vk::GetImpl(context) : nullptr;
if (mSyncHelper->clientWait(vk::GetImpl(display), contextVk, flush,
static_cast<uint64_t>(timeout), &result) == angle::Result::Stop)
if (mSyncHelper->clientWaitUnlocked(vk::GetImpl(display), contextVk, flush,
static_cast<uint64_t>(timeout), outResult,
MapVkResultToEglint) == angle::Result::Stop)
{
return egl::Error(EGL_BAD_ALLOC);
}
switch (result)
{
case VK_EVENT_SET:
// fall through. EGL doesn't differentiate between event being already set, or set
// before timeout.
case VK_SUCCESS:
*outResult = EGL_CONDITION_SATISFIED_KHR;
return egl::NoError();
case VK_TIMEOUT:
*outResult = EGL_TIMEOUT_EXPIRED_KHR;
return egl::NoError();
default:
UNREACHABLE();
*outResult = EGL_FALSE;
return egl::Error(EGL_BAD_ALLOC);
}
return egl::NoError();
}
egl::Error EGLSyncVk::serverWait(const egl::Display *display,

View File

@@ -51,7 +51,8 @@ class ExternalFence final : angle::NonCopyable
int mFenceFd;
};
using SharedExternalFence = std::shared_ptr<ExternalFence>;
using SharedExternalFence = std::shared_ptr<ExternalFence>;
using MapVkResultToApiType = std::function<void(VkResult, angle::Result, void *)>;
class SyncHelperInterface : angle::NonCopyable
{
@@ -65,6 +66,12 @@ class SyncHelperInterface : angle::NonCopyable
bool flushCommands,
uint64_t timeout,
VkResult *outResult) = 0;
virtual angle::Result clientWaitUnlocked(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
void *outResult,
MapVkResultToApiType mappingFunction) = 0;
virtual angle::Result serverWait(ContextVk *contextVk) = 0;
virtual angle::Result getStatus(Context *context, ContextVk *contextVk, bool *signaledOut) = 0;
virtual angle::Result dupNativeFenceFD(Context *context, int *fdOut) const = 0;
@@ -89,7 +96,13 @@ class SyncHelper final : public vk::Resource, public SyncHelperInterface
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *outResult) override;
VkResult *resultOut) override;
angle::Result clientWaitUnlocked(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
void *resultOut,
MapVkResultToApiType mappingFunction) override;
angle::Result serverWait(ContextVk *contextVk) override;
angle::Result getStatus(Context *context, ContextVk *contextVk, bool *signaledOut) override;
angle::Result dupNativeFenceFD(Context *context, int *fdOut) const override
@@ -99,6 +112,11 @@ class SyncHelper final : public vk::Resource, public SyncHelperInterface
private:
angle::Result submitSyncIfDeferred(ContextVk *contextVk, RenderPassClosureReason reason);
angle::Result prepareForClientWait(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *resultOut);
};
// Implementation of sync types: EGLSync(EGL_SYNC_ANDROID_NATIVE_FENCE_ANDROID).
@@ -118,7 +136,13 @@ class SyncHelperNativeFence final : public SyncHelperInterface
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
VkResult *outResult) override;
VkResult *resultOut) override;
angle::Result clientWaitUnlocked(Context *context,
ContextVk *contextVk,
bool flushCommands,
uint64_t timeout,
void *resultOut,
MapVkResultToApiType mappingFunction) override;
angle::Result serverWait(ContextVk *contextVk) override;
angle::Result getStatus(Context *context, ContextVk *contextVk, bool *signaledOut) override;
angle::Result dupNativeFenceFD(Context *context, int *fdOut) const override;

View File

@@ -28,16 +28,37 @@ EGLint ClientWaitSyncKHR(Thread *thread,
EGLint flags,
EGLTimeKHR timeout)
{
ANGLE_EGL_TRY_RETURN(thread, display->prepareForCall(), "eglClientWaitSync",
ANGLE_EGL_TRY_RETURN(thread, display->prepareForCall(), "eglClientWaitSyncKHR",
GetDisplayIfValid(display), EGL_FALSE);
gl::Context *currentContext = thread->getContext();
EGLint syncStatus = EGL_FALSE;
Sync *sync = display->getSync(syncID);
ANGLE_EGL_TRY_RETURN(thread,
sync->clientWait(display, currentContext, flags, timeout, &syncStatus),
"eglClientWaitSync", GetSyncIfValid(display, syncID), EGL_FALSE);
"eglClientWaitSyncKHR", GetSyncIfValid(display, syncID), EGL_FALSE);
thread->setSuccess();
// When performing CPU wait through UnlockedTailCall we need to handle any error conditions
if (egl::Display::GetCurrentThreadUnlockedTailCall()->any())
{
auto handleErrorStatus = [thread, display, syncID](void *result) {
EGLint *eglResult = static_cast<EGLint *>(result);
ASSERT(eglResult);
if (*eglResult == EGL_FALSE)
{
thread->setError(egl::Error(EGL_BAD_ALLOC), "eglClientWaitSyncKHR",
GetSyncIfValid(display, syncID));
}
else
{
thread->setSuccess();
}
};
egl::Display::GetCurrentThreadUnlockedTailCall()->add(handleErrorStatus);
}
else
{
thread->setSuccess();
}
return syncStatus;
}

View File

@@ -104,7 +104,28 @@ EGLint ClientWaitSync(Thread *thread,
thread, syncObject->clientWait(display, currentContext, flags, timeout, &syncStatus),
"eglClientWaitSync", GetSyncIfValid(display, syncID), EGL_FALSE);
thread->setSuccess();
// When performing CPU wait through UnlockedTailCall we need to handle any error conditions
if (egl::Display::GetCurrentThreadUnlockedTailCall()->any())
{
auto handleErrorStatus = [thread, display, syncID](void *result) {
EGLint *eglResult = static_cast<EGLint *>(result);
ASSERT(eglResult);
if (*eglResult == EGL_FALSE)
{
thread->setError(egl::Error(EGL_BAD_ALLOC), "eglClientWaitSync",
GetSyncIfValid(display, syncID));
}
else
{
thread->setSuccess();
}
};
egl::Display::GetCurrentThreadUnlockedTailCall()->add(handleErrorStatus);
}
else
{
thread->setSuccess();
}
return syncStatus;
}

View File

@@ -1002,7 +1002,7 @@ EGLint EGLAPIENTRY EGL_ClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags
ANGLE_CAPTURE_EGL(ClientWaitSync, true, thread, dpyPacked, syncPacked, flags, timeout,
returnValue);
}
ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
egl::Display::GetCurrentThreadUnlockedTailCall()->run(&returnValue);
return returnValue;
}

View File

@@ -1422,7 +1422,7 @@ EGLint EGLAPIENTRY EGL_ClientWaitSyncKHR(EGLDisplay dpy,
ANGLE_CAPTURE_EGL(ClientWaitSyncKHR, true, thread, dpyPacked, syncPacked, flags, timeout,
returnValue);
}
ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
egl::Display::GetCurrentThreadUnlockedTailCall()->run(&returnValue);
return returnValue;
}

View File

@@ -408,7 +408,7 @@ GLenum GL_APIENTRY GL_ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 tim
GenerateContextLostErrorOnCurrentGlobalContext();
returnValue = GetDefaultReturnValue<angle::EntryPoint::GLClientWaitSync, GLenum>();
}
ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
egl::Display::GetCurrentThreadUnlockedTailCall()->run(&returnValue);
return returnValue;
}

View File

@@ -236,6 +236,57 @@ TEST_P(EGLSyncTest, BasicOperations)
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync));
}
// Test that eglClientWaitSync* APIs work.
TEST_P(EGLSyncTest, EglClientWaitSync)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
// Test eglClientWaitSyncKHR
for (size_t i = 0; i < 5; i++)
{
glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(greenProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
ASSERT_GL_NO_ERROR();
// Don't wait forever to make sure the test terminates
constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
EGLSyncKHR clientWaitSync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(clientWaitSync, EGL_NO_SYNC_KHR);
ASSERT_EQ(EGL_CONDITION_SATISFIED_KHR,
eglClientWaitSyncKHR(display, clientWaitSync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
kTimeout));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, clientWaitSync));
ASSERT_EGL_SUCCESS();
}
// Test eglClientWaitSync
for (size_t i = 0; i < 5; i++)
{
glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(greenProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
ASSERT_GL_NO_ERROR();
// Don't wait forever to make sure the test terminates
constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
EGLSyncKHR clientWaitSync = eglCreateSync(display, EGL_SYNC_FENCE, nullptr);
EXPECT_NE(clientWaitSync, EGL_NO_SYNC);
ASSERT_EQ(
EGL_CONDITION_SATISFIED,
eglClientWaitSync(display, clientWaitSync, EGL_SYNC_FLUSH_COMMANDS_BIT, kTimeout));
EXPECT_EGL_TRUE(eglDestroySync(display, clientWaitSync));
ASSERT_EGL_SUCCESS();
}
}
// Test eglWaitClient api
TEST_P(EGLSyncTest, WaitClient)
{

View File

@@ -265,11 +265,26 @@ TEST_P(FenceSyncTest, BasicOperations)
ASSERT_GLENUM_EQ(GL_SIGNALED, value);
ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
for (size_t i = 0; i < 20; i++)
{
glClear(GL_COLOR_BUFFER_BIT);
glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
drawQuad(greenProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
ASSERT_GL_NO_ERROR();
GLsync clientWaitSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
ASSERT_GL_NO_ERROR();
// Don't wait forever to make sure the test terminates
constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
GLenum clientWaitResult =
glClientWaitSync(clientWaitSync, GL_SYNC_FLUSH_COMMANDS_BIT, kTimeout);
EXPECT_GL_NO_ERROR();
EXPECT_TRUE(clientWaitResult == GL_CONDITION_SATISFIED ||
clientWaitResult == GL_ALREADY_SIGNALED);
glDeleteSync(clientWaitSync);
ASSERT_GL_NO_ERROR();
}
}