blob: 9aae69de0aa702458ea25d57173c4a9fba0b13f0 [file] [log] [blame]
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// EGLSyncTest.cpp:
// Tests of EGL_KHR_fence_sync and EGL_KHR_wait_sync extensions.
#include <gtest/gtest.h>
#include "test_utils/ANGLETest.h"
#include "test_utils/angle_test_configs.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include <condition_variable>
using namespace angle;
class EGLSyncTest : public ANGLETest<>
{
protected:
bool hasFenceSyncExtension() const
{
return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_fence_sync");
}
bool hasWaitSyncExtension() const
{
return hasFenceSyncExtension() &&
IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_wait_sync");
}
bool hasGLSyncExtension() const { return IsGLExtensionEnabled("GL_OES_EGL_sync"); }
bool hasAndroidNativeFenceSyncExtension() const
{
return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANDROID_native_fence_sync");
}
};
// Test error cases for all EGL_KHR_fence_sync functions
TEST_P(EGLSyncTest, FenceSyncErrors)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
// If the client API doesn't have the necessary extension, test that sync creation fails and
// ignore the rest of the tests.
if (!hasGLSyncExtension())
{
EXPECT_EQ(EGL_NO_SYNC_KHR, eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr));
EXPECT_EGL_ERROR(EGL_BAD_MATCH);
}
ANGLE_SKIP_TEST_IF(!hasGLSyncExtension());
EGLContext context = eglGetCurrentContext();
EGLSurface drawSurface = eglGetCurrentSurface(EGL_DRAW);
EGLSurface readSurface = eglGetCurrentSurface(EGL_READ);
EXPECT_NE(context, EGL_NO_CONTEXT);
EXPECT_NE(drawSurface, EGL_NO_SURFACE);
EXPECT_NE(readSurface, EGL_NO_SURFACE);
// CreateSync with no attribute shouldn't cause an error
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync));
// CreateSync with empty attribute shouldn't cause an error
const EGLint emptyAttributes[] = {EGL_NONE};
sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, emptyAttributes);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
// DestroySync generates BAD_PARAMETER if the sync is not valid
EXPECT_EGL_FALSE(eglDestroySyncKHR(display, reinterpret_cast<EGLSyncKHR>(20)));
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
// CreateSync generates BAD_DISPLAY if display is not valid
EXPECT_EQ(EGL_NO_SYNC_KHR, eglCreateSyncKHR(EGL_NO_DISPLAY, EGL_SYNC_FENCE_KHR, nullptr));
EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
// CreateSync generates BAD_ATTRIBUTE if attribute is neither nullptr nor empty.
const EGLint nonEmptyAttributes[] = {
EGL_CL_EVENT_HANDLE,
0,
EGL_NONE,
};
EXPECT_EQ(EGL_NO_SYNC_KHR, eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nonEmptyAttributes));
EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
// CreateSync generates BAD_ATTRIBUTE if type is not valid
EXPECT_EQ(EGL_NO_SYNC_KHR, eglCreateSyncKHR(display, 0, nullptr));
EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
// CreateSync generates BAD_MATCH if no context is current
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EXPECT_EQ(EGL_NO_SYNC_KHR, eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr));
EXPECT_EGL_ERROR(EGL_BAD_MATCH);
eglMakeCurrent(display, drawSurface, readSurface, context);
// ClientWaitSync generates EGL_BAD_PARAMETER if the sync object is not valid
EXPECT_EGL_FALSE(eglClientWaitSyncKHR(display, reinterpret_cast<EGLSyncKHR>(30), 0, 0));
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
// GetSyncAttrib generates EGL_BAD_PARAMETER if the sync object is not valid, and value is not
// modified
constexpr EGLint kSentinelAttribValue = 123456789;
EGLint attribValue = kSentinelAttribValue;
EXPECT_EGL_FALSE(eglGetSyncAttribKHR(display, reinterpret_cast<EGLSyncKHR>(40),
EGL_SYNC_TYPE_KHR, &attribValue));
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
EXPECT_EQ(attribValue, kSentinelAttribValue);
// GetSyncAttrib generates EGL_BAD_ATTRIBUTE if the attribute is not valid, and value is not
// modified
EXPECT_EGL_FALSE(eglGetSyncAttribKHR(display, sync, EGL_CL_EVENT_HANDLE, &attribValue));
EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
EXPECT_EQ(attribValue, kSentinelAttribValue);
// GetSyncAttrib generates EGL_BAD_MATCH if the attribute is valid for sync, but not the
// particular sync type. We don't have such a case at the moment.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync));
}
// Test error cases for all EGL_KHR_wait_sync functions
TEST_P(EGLSyncTest, WaitSyncErrors)
{
// The client API that shows support for eglWaitSyncKHR is the same as the one required for
// eglCreateSyncKHR. As such, there is no way to create a sync and not be able to wait on it.
// This would have created an EGL_BAD_MATCH error.
ANGLE_SKIP_TEST_IF(!hasWaitSyncExtension() || !hasGLSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
EGLContext context = eglGetCurrentContext();
EGLSurface drawSurface = eglGetCurrentSurface(EGL_DRAW);
EGLSurface readSurface = eglGetCurrentSurface(EGL_READ);
EXPECT_NE(context, EGL_NO_CONTEXT);
EXPECT_NE(drawSurface, EGL_NO_SURFACE);
EXPECT_NE(readSurface, EGL_NO_SURFACE);
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
// WaitSync generates BAD_MATCH if no context is current
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EXPECT_EGL_FALSE(eglWaitSyncKHR(display, sync, 0));
EXPECT_EGL_ERROR(EGL_BAD_MATCH);
eglMakeCurrent(display, drawSurface, readSurface, context);
// WaitSync generates BAD_PARAMETER if the sync is not valid
EXPECT_EGL_FALSE(eglWaitSyncKHR(display, reinterpret_cast<EGLSyncKHR>(20), 0));
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
// WaitSync generates BAD_PARAMETER if flags is non-zero
EXPECT_EGL_FALSE(eglWaitSyncKHR(display, sync, 1));
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync));
}
// Test usage of eglGetSyncAttribKHR
TEST_P(EGLSyncTest, GetSyncAttrib)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
// Fence sync attributes are:
//
// EGL_SYNC_TYPE_KHR: EGL_SYNC_FENCE_KHR
// EGL_SYNC_STATUS_KHR: EGL_UNSIGNALED_KHR or EGL_SIGNALED_KHR
// EGL_SYNC_CONDITION_KHR: EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR
constexpr EGLint kSentinelAttribValue = 123456789;
EGLint attribValue = kSentinelAttribValue;
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, sync, EGL_SYNC_TYPE_KHR, &attribValue));
EXPECT_EQ(attribValue, EGL_SYNC_FENCE_KHR);
attribValue = kSentinelAttribValue;
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, sync, EGL_SYNC_CONDITION_KHR, &attribValue));
EXPECT_EQ(attribValue, EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR);
attribValue = kSentinelAttribValue;
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, sync, EGL_SYNC_STATUS_KHR, &attribValue));
// Hack around EXPECT_* not having an "either this or that" variant:
if (attribValue != EGL_SIGNALED_KHR)
{
EXPECT_EQ(attribValue, EGL_UNSIGNALED_KHR);
}
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync));
}
// Test that basic usage works and doesn't generate errors or crash
TEST_P(EGLSyncTest, BasicOperations)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_EGL_TRUE(eglWaitSyncKHR(display, sync, 0));
glFlush();
glClear(GL_COLOR_BUFFER_BIT);
// Don't wait forever to make sure the test terminates
constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
EGLint value = 0;
ASSERT_EQ(EGL_CONDITION_SATISFIED_KHR,
eglClientWaitSyncKHR(display, sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, kTimeout));
for (size_t i = 0; i < 20; i++)
{
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_EQ(
EGL_CONDITION_SATISFIED_KHR,
eglClientWaitSyncKHR(display, sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, sync, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
}
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)
{
// Clear to red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_EGL_TRUE(eglWaitClient());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
EGLDisplay display = getEGLWindow()->getDisplay();
EGLContext context = getEGLWindow()->getContext();
EGLSurface surface = getEGLWindow()->getSurface();
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EXPECT_EGL_TRUE(eglWaitClient());
eglMakeCurrent(display, surface, surface, context);
}
// Test eglWaitGL api
TEST_P(EGLSyncTest, WaitGL)
{
// Clear to red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_EGL_TRUE(eglWaitGL());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
EGLDisplay display = getEGLWindow()->getDisplay();
EGLContext context = getEGLWindow()->getContext();
EGLSurface surface = getEGLWindow()->getSurface();
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EXPECT_EGL_TRUE(eglWaitGL());
eglMakeCurrent(display, surface, surface, context);
}
// Test eglWaitNative api
TEST_P(EGLSyncTest, WaitNative)
{
// Clear to red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_EGL_TRUE(eglWaitNative(EGL_CORE_NATIVE_ENGINE));
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
EGLDisplay display = getEGLWindow()->getDisplay();
EGLContext context = getEGLWindow()->getContext();
EGLSurface surface = getEGLWindow()->getSurface();
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EXPECT_EGL_TRUE(eglWaitNative(EGL_CORE_NATIVE_ENGINE));
eglMakeCurrent(display, surface, surface, context);
}
// Verify eglDupNativeFence for EGL_ANDROID_native_fence_sync
TEST_P(EGLSyncTest, AndroidNativeFence_DupNativeFenceFD)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
// We can ClientWait on this
EGLSyncKHR syncWithGeneratedFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EXPECT_NE(syncWithGeneratedFD, EGL_NO_SYNC_KHR);
int fd = eglDupNativeFenceFDANDROID(display, syncWithGeneratedFD);
EXPECT_EGL_SUCCESS();
// Clean up created objects.
if (fd != EGL_NO_NATIVE_FENCE_FD_ANDROID)
{
close(fd);
}
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithGeneratedFD));
}
// Test the validation errors for bad parameters for eglDupNativeFenceFDANDROID
TEST_P(EGLSyncTest, AndroidNativeFence_DupNativeFenceFD_NegativeValidation)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
int fd;
fd = eglDupNativeFenceFDANDROID(display, EGL_NO_SYNC_KHR);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
EXPECT_EQ(fd, EGL_NO_NATIVE_FENCE_FD_ANDROID);
}
// Verify CreateSync and ClientWait for EGL_ANDROID_native_fence_sync
TEST_P(EGLSyncTest, AndroidNativeFence_ClientWait)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLint value = 0;
EGLDisplay display = getEGLWindow()->getDisplay();
// We can ClientWait on this
EGLSyncKHR syncWithGeneratedFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EXPECT_NE(syncWithGeneratedFD, EGL_NO_SYNC_KHR);
// Create work to do
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
// Wait for draw to complete
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSyncKHR(display, syncWithGeneratedFD, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
1'000'000'000));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithGeneratedFD, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
// Clean up created objects.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithGeneratedFD));
}
// Verify WaitSync with EGL_ANDROID_native_fence_sync
// Simulate passing FDs across processes by passing across Contexts.
TEST_P(EGLSyncTest, AndroidNativeFence_WaitSync)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!hasWaitSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLint value = 0;
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSurface surface = getEGLWindow()->getSurface();
/*- First Context ------------------------*/
// We can ClientWait on this
EGLSyncKHR syncWithGeneratedFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EXPECT_NE(syncWithGeneratedFD, EGL_NO_SYNC_KHR);
int fd = eglDupNativeFenceFDANDROID(display, syncWithGeneratedFD);
EXPECT_EGL_SUCCESS(); // Can return -1 (when signaled) or valid FD.
// Create work to do
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
/*- Second Context ------------------------*/
if (fd > EGL_NO_NATIVE_FENCE_FD_ANDROID)
{
EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EGLContext context2 = getEGLWindow()->createContext(EGL_NO_CONTEXT, nullptr);
EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context2));
// We can eglWaitSync on this - import FD from first sync.
EGLint syncAttribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, (EGLint)fd, EGL_NONE};
EGLSyncKHR syncWithDupFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, syncAttribs);
EXPECT_NE(syncWithDupFD, EGL_NO_SYNC_KHR);
// Second draw waits for first to complete. May already be signaled - ignore error.
if (eglWaitSyncKHR(display, syncWithDupFD, 0) == EGL_TRUE)
{
// Create work to do
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
// Wait for second draw to complete
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSyncKHR(display, syncWithDupFD, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
1000000000));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithDupFD, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
// Reset to default context and surface.
EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, getEGLWindow()->getContext()));
// Clean up created objects.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithDupFD));
EXPECT_EGL_TRUE(eglDestroyContext(display, context2));
}
// Wait for first draw to complete
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSyncKHR(display, syncWithGeneratedFD, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
1000000000));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithGeneratedFD, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
// Clean up created objects.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithGeneratedFD));
}
// Verify EGL_ANDROID_native_fence_sync
// Simulate passing FDs across processes by passing across Contexts.
TEST_P(EGLSyncTest, AndroidNativeFence_withFences)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!hasWaitSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLint value = 0;
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSurface surface = getEGLWindow()->getSurface();
/*- First Context ------------------------*/
// Extra fence syncs to ensure that Fence and Android Native fences work together
EGLSyncKHR syncFence1 = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(syncFence1, EGL_NO_SYNC_KHR);
// We can ClientWait on this
EGLSyncKHR syncWithGeneratedFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EXPECT_NE(syncWithGeneratedFD, EGL_NO_SYNC_KHR);
int fd = eglDupNativeFenceFDANDROID(display, syncWithGeneratedFD);
EXPECT_EGL_SUCCESS(); // Can return -1 (when signaled) or valid FD.
EGLSyncKHR syncFence2 = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(syncFence2, EGL_NO_SYNC_KHR);
// Create work to do
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
/*- Second Context ------------------------*/
if (fd > EGL_NO_NATIVE_FENCE_FD_ANDROID)
{
EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EGLContext context2 = getEGLWindow()->createContext(EGL_NO_CONTEXT, nullptr);
EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context2));
// check that Fence and Android fences work together
EGLSyncKHR syncFence3 = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(syncFence3, EGL_NO_SYNC_KHR);
// We can eglWaitSync on this
EGLint syncAttribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, (EGLint)fd, EGL_NONE};
EGLSyncKHR syncWithDupFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, syncAttribs);
EXPECT_NE(syncWithDupFD, EGL_NO_SYNC_KHR);
EGLSyncKHR syncFence4 = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(syncFence4, EGL_NO_SYNC_KHR);
// Second draw waits for first to complete. May already be signaled - ignore error.
if (eglWaitSyncKHR(display, syncWithDupFD, 0) == EGL_TRUE)
{
// Create work to do
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
// Wait for second draw to complete
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSyncKHR(display, syncWithDupFD, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
1000000000));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithDupFD, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
// Reset to default context and surface.
EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, getEGLWindow()->getContext()));
// Clean up created objects.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncFence3));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncFence4));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithDupFD));
EXPECT_EGL_TRUE(eglDestroyContext(display, context2));
}
// Wait for first draw to complete
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSyncKHR(display, syncWithGeneratedFD, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
1000000000));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithGeneratedFD, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
// Clean up created objects.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncFence1));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncFence2));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithGeneratedFD));
}
// Verify that VkSemaphore is not destroyed before used for waiting
TEST_P(EGLSyncTest, AndroidNativeFence_VkSemaphoreDestroyBug)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!hasWaitSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
glFinish(); // Ensure no pending commands
EGLSyncKHR syncWithGeneratedFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EXPECT_NE(syncWithGeneratedFD, EGL_NO_SYNC_KHR);
EXPECT_EGL_TRUE(eglWaitSyncKHR(display, syncWithGeneratedFD, 0));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithGeneratedFD));
glFinish(); // May destroy VkSemaphore if bug is present.
// Create work to do
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFinish(); // Will submit destroyed Semaphores.
}
// Verify that no VVL errors are generated when External Fence Handle is used to track submissions
TEST_P(EGLSyncTest, AndroidNativeFence_ExternalFenceWaitVVLBug)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(!hasAndroidNativeFenceSyncExtension());
EGLint value = 0;
EGLDisplay display = getEGLWindow()->getDisplay();
// Create work to do
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
ASSERT_GL_NO_ERROR();
// We can ClientWait on this
EGLSyncKHR syncWithGeneratedFD =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EXPECT_NE(syncWithGeneratedFD, EGL_NO_SYNC_KHR);
// Wait for draw to complete
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSyncKHR(display, syncWithGeneratedFD, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
1'000'000'000));
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithGeneratedFD, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
// Clean up created objects.
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, syncWithGeneratedFD));
// Finish to cleanup internal garbage in the backend.
glFinish();
}
// Test functionality of EGL_ANGLE_global_fence_sync.
TEST_P(EGLSyncTest, GlobalFenceSync)
{
EGLDisplay display = getEGLWindow()->getDisplay();
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_global_fence_sync"));
// Create a second context
EGLContext context1 = eglGetCurrentContext();
EGLSurface drawSurface1 = eglGetCurrentSurface(EGL_DRAW);
EGLSurface readSurface1 = eglGetCurrentSurface(EGL_READ);
EGLConfig config = getEGLWindow()->getConfig();
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, getEGLWindow()->getClientMajorVersion(),
EGL_CONTEXT_MINOR_VERSION_KHR, getEGLWindow()->getClientMinorVersion(), EGL_NONE};
EGLContext context2 = eglCreateContext(display, config, context1, contextAttribs);
ASSERT_NE(EGL_NO_CONTEXT, context2);
const EGLint pbufferAttribs[] = {EGL_WIDTH, getWindowWidth(), EGL_HEIGHT, getWindowHeight(),
EGL_NONE};
EGLSurface drawSurface2 = eglCreatePbufferSurface(display, config, pbufferAttribs);
ASSERT_NE(EGL_NO_SURFACE, drawSurface2);
// Do an expensive draw in context 2
eglMakeCurrent(display, drawSurface2, drawSurface2, context2);
constexpr char kCostlyVS[] = R"(attribute highp vec4 position;
varying highp vec4 testPos;
void main(void)
{
testPos = position;
gl_Position = position;
})";
constexpr char kCostlyFS[] = R"(precision highp float;
varying highp vec4 testPos;
void main(void)
{
vec4 test = testPos;
for (int i = 0; i < 500; i++)
{
test = sqrt(test);
}
gl_FragColor = test;
})";
ANGLE_GL_PROGRAM(expensiveProgram, kCostlyVS, kCostlyFS);
drawQuad(expensiveProgram, "position", 0.0f);
// Signal a fence sync for testing
EGLSyncKHR sync2 = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
// Switch to context 1, and create a global fence sync
eglMakeCurrent(display, drawSurface1, readSurface1, context1);
EGLSyncKHR sync1 = eglCreateSyncKHR(display, EGL_SYNC_GLOBAL_FENCE_ANGLE, nullptr);
// Wait for the global fence sync to finish.
constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
ASSERT_EQ(EGL_CONDITION_SATISFIED_KHR, eglClientWaitSyncKHR(display, sync1, 0, kTimeout));
// If the global fence sync is signaled, then the signal from context2 must also be signaled.
// Note that if sync1 was an EGL_SYNC_FENCE_KHR, this would not necessarily be true.
EGLint value = 0;
EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, sync2, EGL_SYNC_STATUS_KHR, &value));
EXPECT_EQ(value, EGL_SIGNALED_KHR);
EXPECT_EQ(EGL_CONDITION_SATISFIED_KHR, eglClientWaitSyncKHR(display, sync2, 0, 0));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync1));
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync2));
EXPECT_EGL_TRUE(eglDestroySurface(display, drawSurface2));
EXPECT_EGL_TRUE(eglDestroyContext(display, context2));
}
// Test that leaked fences are cleaned up in a safe way. Regression test for sync objects using tail
// calls for destruction.
TEST_P(EGLSyncTest, DISABLED_LeakSyncToDisplayDestruction)
{
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
}
// Test the validation errors for bad parameters for eglCreateSyncKHR
TEST_P(EGLSyncTest, NegativeValidationBadAttributes)
{
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSyncKHR sync;
const EGLint invalidCreateSyncAttributeList[][3] = {
{EGL_SYNC_CONDITION_KHR, EGL_NONE, 0},
{EGL_SYNC_CONDITION_KHR, EGL_RENDERABLE_TYPE, EGL_NONE},
{EGL_SYNC_CONDITION_KHR, EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR, EGL_RENDERABLE_TYPE},
};
for (size_t i = 0; i < 3; i++)
{
sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, &invalidCreateSyncAttributeList[i][0]);
ASSERT_EQ(sync, EGL_NO_SYNC_KHR);
ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
}
}
// Tests that eglClientWaitSyncKHR() is not blocking when Vulkan CommandQueue performs CPU
// throttling during submission (when `kInFlightCommandsLimit` is exceeded).
TEST_P(EGLSyncTest, BlockingOnSubmitCPUThrottling)
{
ANGLE_SKIP_TEST_IF(!isVulkanRenderer() || isSwiftshader());
ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
// Should be somewhat larger than the `kInFlightCommandsLimit`. At the same time,
// `kInFlightCommandsLimit` should be less than the internal driver limit, otherwise test will
// not work.
constexpr size_t kMaxSyncCount = 100;
constexpr GLsizei kBufferResolution = 1024;
constexpr size_t kDrawsPerSync = 2;
constexpr double kLongWaitThresholdMs = 5.0;
constexpr size_t kMinLongWaitsToFail = 5;
constexpr char kCostlyVS[] = R"(attribute highp vec4 position;
varying highp vec4 testPos;
void main(void)
{
testPos = position;
gl_Position = position;
})";
constexpr char kCostlyFS[] = R"(precision highp float;
varying highp vec4 testPos;
void main(void)
{
vec4 test = testPos;
for (int i = 0; i < 500; i++)
{
test = sqrt(test);
}
gl_FragColor = test;
})";
std::array<EGLSyncKHR, kMaxSyncCount> syncArray;
size_t syncCount = 0;
std::mutex mutex;
std::condition_variable condVar;
size_t numLongWaits = 0;
bool isSyncWaitThreadReady = false;
EGLDisplay display = getEGLWindow()->getDisplay();
std::thread syncWaitThread([&]() {
constexpr GLuint64 kTimeout = 0; // Just check status
EGLConfig config = getEGLWindow()->getConfig();
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, getEGLWindow()->getClientMajorVersion(),
EGL_CONTEXT_MINOR_VERSION_KHR, getEGLWindow()->getClientMinorVersion(), EGL_NONE};
EGLContext context2 = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
ASSERT_NE(EGL_NO_CONTEXT, context2);
const EGLint pbufferAttribs[] = {EGL_WIDTH, getWindowWidth(), EGL_HEIGHT, getWindowHeight(),
EGL_NONE};
EGLSurface drawSurface2 = eglCreatePbufferSurface(display, config, pbufferAttribs);
ASSERT_NE(EGL_NO_SURFACE, drawSurface2);
// Making some Context current just to prevent blocking in ANGLE_CAPTURE_EGL
EXPECT_EGL_TRUE(eglMakeCurrent(display, drawSurface2, drawSurface2, context2));
{
std::unique_lock<std::mutex> lock(mutex);
isSyncWaitThreadReady = true;
}
condVar.notify_one();
for (size_t syncIndex = 0; syncIndex < kMaxSyncCount; ++syncIndex)
{
{
std::unique_lock<std::mutex> lock(mutex);
condVar.wait(lock, [&]() { return syncIndex < syncCount; });
}
while (true)
{
const double t1 = angle::GetCurrentSystemTime();
EGLint result = eglClientWaitSyncKHR(display, syncArray[syncIndex], 0, kTimeout);
const double t2 = angle::GetCurrentSystemTime();
if ((t2 - t1) * 1000.0 > kLongWaitThresholdMs)
{
++numLongWaits;
}
if (result != EGL_TIMEOUT_EXPIRED_KHR)
{
EXPECT_EQ(result, EGL_CONDITION_SATISFIED_KHR);
break;
}
// Wait some time and try again...
std::this_thread::sleep_for(std::chrono::microseconds(1000));
}
}
EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_TRUE(eglDestroySurface(display, drawSurface2));
EXPECT_EGL_TRUE(eglDestroyContext(display, context2));
});
// Prepare the framebuffer
GLFramebuffer framebuffer;
GLTexture fbTexture;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glBindTexture(GL_TEXTURE_2D, fbTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kBufferResolution, kBufferResolution);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbTexture, 0);
glBindTexture(GL_TEXTURE_2D, 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
glViewport(0, 0, kBufferResolution, kBufferResolution);
ANGLE_GL_PROGRAM(program, kCostlyVS, kCostlyFS);
// Wait until thread is ready waiting on EGL sync objects...
{
std::unique_lock<std::mutex> lock(mutex);
condVar.wait(lock, [&]() { return isSyncWaitThreadReady; });
}
while (syncCount < kMaxSyncCount)
{
// Perform GPU heavy rendering
for (size_t i = 0; i < kDrawsPerSync; ++i)
{
drawQuad(program, "position", 0.0f);
ASSERT_GL_NO_ERROR();
}
// Using glFenceSync() to force submission without also blocking on the EGL Global mutex.
GLsync clientWaitSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
ASSERT_GL_NO_ERROR();
constexpr GLuint64 kTimeout = 0; // Just check status
glClientWaitSync(clientWaitSync, GL_SYNC_FLUSH_COMMANDS_BIT, kTimeout);
EXPECT_GL_NO_ERROR();
glDeleteSync(clientWaitSync);
ASSERT_GL_NO_ERROR();
// Creating EGL sync should not block on submission, since glClientWaitSync() should have
// already done that.
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC_KHR);
// Make sync ready for checking from another thread.
syncArray[syncCount] = sync;
{
std::unique_lock<std::mutex> lock(mutex);
++syncCount;
}
condVar.notify_one();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
syncWaitThread.join();
for (EGLSyncKHR sync : syncArray)
{
EXPECT_EGL_TRUE(eglDestroySyncKHR(display, sync));
}
EXPECT_LT(numLongWaits, kMinLongWaitsToFail);
}
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(EGLSyncTest);