| // | |
| // 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 "common/unsafe_buffers.h" | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/angle_test_configs.h" | |
| #include "test_utils/gl_raii.h" | |
| #include "util/EGLWindow.h" | |
| #include <array> | |
| #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); | |
| // Clean up created objects. | |
| if (fd != EGL_NO_NATIVE_FENCE_FD_ANDROID) | |
| { | |
| EXPECT_EGL_SUCCESS(); | |
| close(fd); | |
| } | |
| else | |
| { | |
| // For some driver, eglDupNativeFenceFDANDROID() may return -1 with EGL_BAD_PARAMETER, if | |
| // the sync has been signalled. | |
| EXPECT_EGL_ERROR(EGL_BAD_PARAMETER); | |
| EGLint value; | |
| EXPECT_EGL_TRUE( | |
| eglGetSyncAttribKHR(display, syncWithGeneratedFD, EGL_SYNC_STATUS_KHR, &value)); | |
| EXPECT_EQ(value, EGL_SIGNALED_KHR); | |
| EXPECT_EGL_TRUE(eglGetSyncAttribKHR(display, syncWithGeneratedFD, | |
| EGL_SYNC_NATIVE_FENCE_FD_ANDROID, &value)); | |
| EXPECT_EQ(value, EGL_NO_NATIVE_FENCE_FD_ANDROID); | |
| } | |
| 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 = 2'000'000'000; // 2 seconds | |
| 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 functionality of EGL_ANGLE_global_fence_sync in the presence of multiple threads. | |
| TEST_P(EGLSyncTest, GlobalFenceSyncMultithreaded) | |
| { | |
| 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); | |
| EGLSyncKHR sync2 = EGL_NO_SYNC_KHR; | |
| // Do an expensive draw in context 2, in a thread | |
| std::thread slowSubmit([&]() { | |
| 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 | |
| sync2 = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr); | |
| // Release the context. In the Vulkan backend, this frees the queue index assigned to the | |
| // context. | |
| eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| }); | |
| slowSubmit.join(); | |
| // Re-make-current the context. In the Vulkan backend, this realloces the queue index assigned | |
| // to the context. | |
| eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| eglMakeCurrent(display, drawSurface1, readSurface1, context1); | |
| // Create a global fence sync | |
| EGLSyncKHR sync1 = eglCreateSyncKHR(display, EGL_SYNC_GLOBAL_FENCE_ANGLE, nullptr); | |
| // Wait for the global fence sync to finish. | |
| constexpr GLuint64 kTimeout = 2'000'000'000; // 2 seconds | |
| 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; | |
| static constexpr std::array<std::array<EGLint, 3>, 3> invalidCreateSyncAttributeList = {{ | |
| {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].data()); | |
| 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_AND(EGLSyncTest, | |
| ES2_VULKAN().enable(Feature::EnableExtraSubmitFence), | |
| ES3_VULKAN().enable(Feature::EnableExtraSubmitFence)); |