| // |
| // Copyright 2016 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. |
| // |
| // EGLContextSharingTest.cpp: |
| // Tests relating to shared Contexts. |
| |
| #include <gtest/gtest.h> |
| |
| #include "common/tls.h" |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/MultiThreadSteps.h" |
| #include "test_utils/angle_test_configs.h" |
| #include "test_utils/gl_raii.h" |
| #include "util/EGLWindow.h" |
| #include "util/OSWindow.h" |
| #include "util/test_utils.h" |
| |
| using namespace angle; |
| |
| namespace |
| { |
| |
| EGLBoolean SafeDestroyContext(EGLDisplay display, EGLContext &context) |
| { |
| EGLBoolean result = EGL_TRUE; |
| if (context != EGL_NO_CONTEXT) |
| { |
| result = eglDestroyContext(display, context); |
| context = EGL_NO_CONTEXT; |
| } |
| return result; |
| } |
| |
| class EGLContextSharingTest : public ANGLETest<> |
| { |
| public: |
| EGLContextSharingTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) {} |
| |
| void testTearDown() override |
| { |
| glDeleteTextures(1, &mTexture); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| if (display != EGL_NO_DISPLAY) |
| { |
| for (auto &context : mContexts) |
| { |
| SafeDestroyContext(display, context); |
| } |
| } |
| |
| // Set default test state to not give an error on shutdown. |
| getEGLWindow()->makeCurrent(); |
| } |
| |
| EGLContext mContexts[2] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT}; |
| GLuint mTexture; |
| }; |
| |
| class EGLContextSharingTestNoFixture : public EGLContextSharingTest |
| { |
| public: |
| EGLContextSharingTestNoFixture() : EGLContextSharingTest() {} |
| |
| void testSetUp() override |
| { |
| mOsWindow = OSWindow::New(); |
| mMajorVersion = GetParam().majorVersion; |
| } |
| |
| void testTearDown() override |
| { |
| if (mDisplay != EGL_NO_DISPLAY) |
| { |
| eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| |
| if (mSurface != EGL_NO_SURFACE) |
| { |
| eglDestroySurface(mDisplay, mSurface); |
| ASSERT_EGL_SUCCESS(); |
| mSurface = EGL_NO_SURFACE; |
| } |
| |
| for (auto &context : mContexts) |
| { |
| SafeDestroyContext(mDisplay, context); |
| } |
| |
| eglTerminate(mDisplay); |
| mDisplay = EGL_NO_DISPLAY; |
| ASSERT_EGL_SUCCESS(); |
| eglReleaseThread(); |
| ASSERT_EGL_SUCCESS(); |
| } |
| |
| mOsWindow->destroy(); |
| OSWindow::Delete(&mOsWindow); |
| ASSERT_EGL_SUCCESS() << "Error during test TearDown"; |
| } |
| |
| bool chooseConfig(EGLConfig *config) const |
| { |
| bool result = false; |
| EGLint count = 0; |
| EGLint clientVersion = mMajorVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT; |
| EGLint attribs[] = {EGL_RED_SIZE, |
| 8, |
| EGL_GREEN_SIZE, |
| 8, |
| EGL_BLUE_SIZE, |
| 8, |
| EGL_ALPHA_SIZE, |
| 8, |
| EGL_RENDERABLE_TYPE, |
| clientVersion, |
| EGL_SURFACE_TYPE, |
| EGL_WINDOW_BIT | EGL_PBUFFER_BIT, |
| EGL_NONE}; |
| |
| result = eglChooseConfig(mDisplay, attribs, config, 1, &count); |
| EXPECT_EGL_TRUE(result && (count > 0)); |
| return result; |
| } |
| |
| bool createContext(EGLConfig config, |
| EGLContext *context, |
| EGLContext share_context = EGL_NO_CONTEXT) |
| { |
| bool result = false; |
| EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, mMajorVersion, |
| EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, mVirtualizationGroup++, |
| EGL_NONE}; |
| |
| if (!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_context_virtualization")) |
| { |
| attribs[2] = EGL_NONE; |
| } |
| |
| *context = eglCreateContext(mDisplay, config, share_context, attribs); |
| result = (*context != EGL_NO_CONTEXT); |
| EXPECT_TRUE(result); |
| return result; |
| } |
| |
| bool createWindowSurface(EGLConfig config, EGLNativeWindowType win, EGLSurface *surface) |
| { |
| bool result = false; |
| EGLint attribs[] = {EGL_NONE}; |
| |
| *surface = eglCreateWindowSurface(mDisplay, config, win, attribs); |
| result = (*surface != EGL_NO_SURFACE); |
| EXPECT_TRUE(result); |
| return result; |
| } |
| |
| bool createPbufferSurface(EGLDisplay dpy, |
| EGLConfig config, |
| EGLint width, |
| EGLint height, |
| EGLSurface *surface) |
| { |
| bool result = false; |
| EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; |
| |
| *surface = eglCreatePbufferSurface(dpy, config, attribs); |
| result = (*surface != EGL_NO_SURFACE); |
| EXPECT_TRUE(result); |
| return result; |
| } |
| |
| OSWindow *mOsWindow; |
| EGLDisplay mDisplay = EGL_NO_DISPLAY; |
| EGLSurface mSurface = EGL_NO_SURFACE; |
| const EGLint kWidth = 64; |
| const EGLint kHeight = 64; |
| EGLint mMajorVersion = 0; |
| std::atomic<EGLint> mVirtualizationGroup; |
| }; |
| |
| // Tests that creating resources works after freeing the share context. |
| TEST_P(EGLContextSharingTest, BindTextureAfterShareContextFree) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLSurface surface = getEGLWindow()->getSurface(); |
| |
| const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, |
| getEGLWindow()->getClientMajorVersion(), EGL_NONE}; |
| |
| mContexts[0] = eglCreateContext(display, config, nullptr, contextAttribs); |
| ASSERT_EGL_SUCCESS(); |
| ASSERT_TRUE(mContexts[0] != EGL_NO_CONTEXT); |
| mContexts[1] = eglCreateContext(display, config, mContexts[1], contextAttribs); |
| ASSERT_EGL_SUCCESS(); |
| ASSERT_TRUE(mContexts[1] != EGL_NO_CONTEXT); |
| |
| ASSERT_EGL_TRUE(SafeDestroyContext(display, mContexts[0])); |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1])); |
| ASSERT_EGL_SUCCESS(); |
| |
| glGenTextures(1, &mTexture); |
| glBindTexture(GL_TEXTURE_2D, mTexture); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Tests the creation of contexts using EGL_ANGLE_display_texture_share_group |
| TEST_P(EGLContextSharingTest, DisplayShareGroupContextCreation) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| EGLConfig config = getEGLWindow()->getConfig(); |
| |
| const EGLint inShareGroupContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE}; |
| |
| // Check whether extension's supported to avoid clearing the EGL error state |
| // after failed context creation. |
| bool extensionEnabled = |
| IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group"); |
| |
| // Test creating two contexts in the global share group |
| mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| mContexts[1] = eglCreateContext(display, config, mContexts[1], inShareGroupContextAttribs); |
| |
| if (!extensionEnabled) |
| { |
| // Make sure an error is generated and early-exit |
| ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE); |
| ASSERT_EQ(EGL_NO_CONTEXT, mContexts[0]); |
| return; |
| } |
| |
| ASSERT_EGL_SUCCESS(); |
| |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]); |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]); |
| eglDestroyContext(display, mContexts[0]); |
| mContexts[0] = EGL_NO_CONTEXT; |
| |
| // Try creating a context that is not in the global share group but tries to share with a |
| // context that is |
| const EGLint notInShareGroupContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_FALSE, EGL_NONE}; |
| mContexts[0] = eglCreateContext(display, config, mContexts[1], notInShareGroupContextAttribs); |
| ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE); |
| ASSERT_TRUE(mContexts[0] == EGL_NO_CONTEXT); |
| } |
| |
| // Tests the sharing of textures using EGL_ANGLE_display_texture_share_group |
| TEST_P(EGLContextSharingTest, DisplayShareGroupObjectSharing) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| ANGLE_SKIP_TEST_IF( |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group")); |
| |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLSurface surface = getEGLWindow()->getSurface(); |
| |
| const EGLint inShareGroupContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE}; |
| |
| // Create two contexts in the global share group but not in the same context share group |
| mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| mContexts[1] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| |
| ASSERT_EGL_SUCCESS(); |
| |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]); |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]); |
| |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| ASSERT_EGL_SUCCESS(); |
| |
| // Create a texture and buffer in ctx 0 |
| GLTexture textureFromCtx0; |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx0); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| |
| GLBuffer bufferFromCtx0; |
| glBindBuffer(GL_ARRAY_BUFFER, bufferFromCtx0); |
| glBufferData(GL_ARRAY_BUFFER, 1, nullptr, GL_STATIC_DRAW); |
| glBindBuffer(GL_ARRAY_BUFFER, 0); |
| ASSERT_GL_TRUE(glIsBuffer(bufferFromCtx0)); |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| // Switch to context 1 and verify that the texture is accessible but the buffer is not |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1])); |
| ASSERT_EGL_SUCCESS(); |
| |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| |
| ASSERT_GL_FALSE(glIsBuffer(bufferFromCtx0)); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Call readpixels on the texture to verify that the backend has proper support |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureFromCtx0, 0); |
| |
| GLubyte pixel[4]; |
| glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Switch back to context 0 and delete the buffer |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| ASSERT_EGL_SUCCESS(); |
| } |
| |
| // Tests that shared textures using EGL_ANGLE_display_texture_share_group are released when the last |
| // context is destroyed |
| TEST_P(EGLContextSharingTest, DisplayShareGroupReleasedWithLastContext) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| ANGLE_SKIP_TEST_IF( |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group")); |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLSurface surface = getEGLWindow()->getSurface(); |
| |
| const EGLint inShareGroupContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE}; |
| |
| // Create two contexts in the global share group but not in the same context share group |
| mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| mContexts[1] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| |
| // Create a texture and buffer in ctx 0 |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| GLTexture textureFromCtx0; |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx0); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| |
| // Switch to context 1 and verify that the texture is accessible |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1])); |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| |
| // Destroy both contexts, the texture should be cleaned up automatically |
| ASSERT_EGL_TRUE(eglDestroyContext(display, mContexts[0])); |
| mContexts[0] = EGL_NO_CONTEXT; |
| ASSERT_EGL_TRUE(eglDestroyContext(display, mContexts[1])); |
| mContexts[1] = EGL_NO_CONTEXT; |
| |
| // Unmake current, so the context can be released. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| |
| // Create a new context and verify it cannot access the texture previously created |
| mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| |
| ASSERT_GL_FALSE(glIsTexture(textureFromCtx0)); |
| } |
| |
| // Tests that after creating a texture using EGL_ANGLE_display_texture_share_group, |
| // and deleting the Context and the egl::ShareGroup who own a texture staged updates, |
| // the texture staged updates are flushed, and the Context and egl::ShareGroup can be destroyed |
| // successfully, and the texture can still be accessed from the global display texture share group |
| TEST_P(EGLContextSharingTest, DisplayShareGroupReleaseShareGroupThatOwnsStagedUpdates) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| ANGLE_SKIP_TEST_IF( |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group")); |
| |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLSurface surface = getEGLWindow()->getSurface(); |
| |
| const EGLint inShareGroupContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE}; |
| |
| // Create two contexts in the global share group, but not in the same context share group |
| EGLContext context1 = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| EGLContext context2 = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| |
| // Create a texture in context1 and stage a texture update |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context1)); |
| constexpr GLsizei kTexSize = 2; |
| const GLColor kBlueData[kTexSize * kTexSize] = {GLColor::blue, GLColor::blue, GLColor::blue, |
| GLColor::blue}; |
| GLTexture textureFromCtx0; |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx0); |
| // This will stage a texture update in context1's SharedGroup::BufferPool |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, kBlueData); |
| |
| // Destroy context 1, this also destroys context1's SharedGroup and BufferPool |
| // The texture staged update in context1's SharedGroup BufferPool will be flushed |
| SafeDestroyContext(display, context1); |
| |
| // Switch to context2 and verify that the texture is accessible |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context2)); |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| |
| // Sample from textureFromCtx0 and check it works properly |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx0); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| ANGLE_GL_PROGRAM(program1, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(program1); |
| drawQuad(program1, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue); |
| |
| // Destroy context2 |
| eglDestroyContext(display, context2); |
| } |
| |
| // Tests that after creating a texture using EGL_ANGLE_display_texture_share_group, |
| // and use it for sampling, and then deleting the Context (which destroys shareGroup) works. If |
| // anything cached in ShareGroup, it should be handled nicely if texture can outlive ShareGroup (for |
| // example, bugs like angleproject:7466). |
| TEST_P(EGLContextSharingTest, DisplayShareGroupReleaseShareGroupThenDestroyTexture) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| ANGLE_SKIP_TEST_IF( |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group")); |
| |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLSurface surface = getEGLWindow()->getSurface(); |
| |
| const EGLint inShareGroupContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE}; |
| |
| // Create two contexts in the global share group, but not in the same context share group |
| EGLContext context1 = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| EGLContext context2 = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); |
| |
| // Create a texture in context1 and stage a texture update |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context1)); |
| constexpr GLsizei kTexSize = 2; |
| const GLColor kBlueData[kTexSize * kTexSize] = {GLColor::blue, GLColor::blue, GLColor::blue, |
| GLColor::blue}; |
| { |
| GLTexture textureFromCtx1; |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx1); |
| // This will stage a texture update in context1's SharedGroup::BufferPool |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, kBlueData); |
| |
| // Sample from textureFromCtx1 and check it works properly |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx1); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| ANGLE_GL_PROGRAM(program1, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(program1); |
| drawQuad(program1, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue); |
| |
| // Destroy context 1, this also destroys context1's SharedGroup and BufferPool |
| // The texture staged update in context1's SharedGroup BufferPool will be flushed |
| SafeDestroyContext(display, context1); |
| |
| // Switch to context2 and verify that the texture is accessible |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context2)); |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx1)); |
| |
| // textureFromCtx1 should be destroyed when leaving this scope |
| } |
| |
| // Destroy context2 |
| eglDestroyContext(display, context2); |
| } |
| |
| // Tests that deleting an object on one Context doesn't destroy it ahead-of-time. Mostly focused |
| // on the Vulkan back-end where we manage object lifetime manually. |
| TEST_P(EGLContextSharingTest, TextureLifetime) |
| { |
| EGLWindow *eglWindow = getEGLWindow(); |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| // Create a pbuffer surface for use with a shared context. |
| EGLSurface surface = eglWindow->getSurface(); |
| EGLContext mainContext = eglWindow->getContext(); |
| |
| // Initialize a shared context. |
| mContexts[0] = eglCreateContext(display, config, mainContext, nullptr); |
| ASSERT_NE(mContexts[0], EGL_NO_CONTEXT); |
| |
| // Create a Texture on the shared context. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| |
| constexpr GLsizei kTexSize = 2; |
| const GLColor kTexData[kTexSize * kTexSize] = {GLColor::red, GLColor::green, GLColor::blue, |
| GLColor::yellow}; |
| GLTexture tex; |
| glBindTexture(GL_TEXTURE_2D, tex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| kTexData); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| |
| // Make the main Context current and draw with the texture. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext)); |
| |
| glBindTexture(GL_TEXTURE_2D, tex); |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(program); |
| |
| // No uniform update because the update seems to hide the error on Vulkan. |
| |
| // Enqueue the draw call. |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Delete the texture in the main context to orphan it. |
| // Do not read back the data to keep the commands in the graph. |
| tex.reset(); |
| |
| // Bind and delete the test context. This should trigger texture garbage collection. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| SafeDestroyContext(display, mContexts[0]); |
| |
| // Bind the main context to clean up the test. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext)); |
| } |
| |
| // Tests that deleting an object on one Context doesn't destroy it ahead-of-time. Mostly focused |
| // on the Vulkan back-end where we manage object lifetime manually. |
| TEST_P(EGLContextSharingTest, SamplerLifetime) |
| { |
| EGLWindow *eglWindow = getEGLWindow(); |
| EGLConfig config = getEGLWindow()->getConfig(); |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); |
| ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_KHR_create_context")); |
| |
| // Create a pbuffer surface for use with a shared context. |
| EGLSurface surface = eglWindow->getSurface(); |
| EGLContext mainContext = eglWindow->getContext(); |
| |
| std::vector<EGLint> contextAttributes; |
| contextAttributes.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR); |
| contextAttributes.push_back(getClientMajorVersion()); |
| contextAttributes.push_back(EGL_NONE); |
| |
| // Initialize a shared context. |
| mContexts[0] = eglCreateContext(display, config, mainContext, contextAttributes.data()); |
| ASSERT_NE(mContexts[0], EGL_NO_CONTEXT); |
| |
| // Create a Texture on the shared context. Also create a Sampler object. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| |
| constexpr GLsizei kTexSize = 2; |
| const GLColor kTexData[kTexSize * kTexSize] = {GLColor::red, GLColor::green, GLColor::blue, |
| GLColor::yellow}; |
| GLTexture tex; |
| glBindTexture(GL_TEXTURE_2D, tex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| kTexData); |
| |
| GLSampler sampler; |
| glBindSampler(0, sampler); |
| glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| |
| // Make the main Context current and draw with the texture and sampler. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext)); |
| |
| glBindTexture(GL_TEXTURE_2D, tex); |
| glBindSampler(0, sampler); |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(program); |
| |
| // No uniform update because the update seems to hide the error on Vulkan. |
| |
| // Enqueue the draw call. |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Delete the texture and sampler in the main context to orphan them. |
| // Do not read back the data to keep the commands in the graph. |
| tex.reset(); |
| sampler.reset(); |
| |
| // Bind and delete the test context. This should trigger texture garbage collection. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); |
| SafeDestroyContext(display, mContexts[0]); |
| |
| // Bind the main context to clean up the test. |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext)); |
| } |
| |
| // Test that deleting an object reading from a shared object in one context doesn't cause the other |
| // context to crash. Mostly focused on the Vulkan back-end where we track resource dependencies in |
| // a graph. |
| TEST_P(EGLContextSharingTest, DeleteReaderOfSharedTexture) |
| { |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); |
| // GL Fences require GLES 3.0+ |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); |
| |
| // Initialize contexts |
| EGLWindow *window = getEGLWindow(); |
| EGLDisplay dpy = window->getDisplay(); |
| EGLConfig config = window->getConfig(); |
| |
| constexpr size_t kThreadCount = 2; |
| EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE}; |
| EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT}; |
| |
| EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE}; |
| |
| for (size_t t = 0; t < kThreadCount; ++t) |
| { |
| surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes); |
| EXPECT_EGL_SUCCESS(); |
| |
| ctx[t] = window->createContext(t == 0 ? EGL_NO_CONTEXT : ctx[0], nullptr); |
| EXPECT_NE(EGL_NO_CONTEXT, ctx[t]); |
| } |
| |
| // Initialize test resources. They are done outside the threads to reduce the sources of |
| // errors and thus deadlock-free teardown. |
| ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1])); |
| |
| // Shared texture to read from. |
| constexpr GLsizei kTexSize = 1; |
| const GLColor kTexData = GLColor::red; |
| |
| GLTexture sharedTex; |
| glBindTexture(GL_TEXTURE_2D, sharedTex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| &kTexData); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| |
| // Resources for each context. |
| GLRenderbuffer renderbuffer[kThreadCount]; |
| GLFramebuffer fbo[kThreadCount]; |
| GLProgram program[kThreadCount]; |
| |
| for (size_t t = 0; t < kThreadCount; ++t) |
| { |
| ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[t], surface[t], ctx[t])); |
| |
| glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[t]); |
| constexpr int kRenderbufferSize = 4; |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo[t]); |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, |
| renderbuffer[t]); |
| |
| glBindTexture(GL_TEXTURE_2D, sharedTex); |
| program[t].makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| ASSERT_TRUE(program[t].valid()); |
| } |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. |
| std::mutex mutex; |
| std::condition_variable condVar; |
| std::atomic<GLsync> deletingThreadSyncObj; |
| std::atomic<GLsync> continuingThreadSyncObj; |
| |
| enum class Step |
| { |
| Start, |
| Thread0Draw, |
| Thread1Draw, |
| Thread0Delete, |
| Finish, |
| Abort, |
| }; |
| Step currentStep = Step::Start; |
| |
| std::thread deletingThread = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0])); |
| EXPECT_EGL_SUCCESS(); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); |
| |
| // Draw using the shared texture. |
| drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f); |
| |
| deletingThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); |
| ASSERT_GL_NO_ERROR(); |
| // Force the fence to be created |
| glFlush(); |
| |
| // Wait for the other thread to also draw using the shared texture. |
| threadSynchronization.nextStep(Step::Thread0Draw); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Draw)); |
| |
| ASSERT_TRUE(continuingThreadSyncObj != nullptr); |
| glWaitSync(continuingThreadSyncObj, 0, GL_TIMEOUT_IGNORED); |
| ASSERT_GL_NO_ERROR(); |
| glDeleteSync(continuingThreadSyncObj); |
| ASSERT_GL_NO_ERROR(); |
| continuingThreadSyncObj = nullptr; |
| |
| // Delete this thread's framebuffer (reader of the shared texture). |
| fbo[0].reset(); |
| |
| // Wait for the other thread to use the shared texture again before unbinding the |
| // context (so no implicit flush happens). |
| threadSynchronization.nextStep(Step::Thread0Delete); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| EXPECT_EGL_SUCCESS(); |
| }); |
| |
| std::thread continuingThread = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1])); |
| EXPECT_EGL_SUCCESS(); |
| |
| // Wait for first thread to draw using the shared texture. |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Draw)); |
| |
| ASSERT_TRUE(deletingThreadSyncObj != nullptr); |
| glWaitSync(deletingThreadSyncObj, 0, GL_TIMEOUT_IGNORED); |
| ASSERT_GL_NO_ERROR(); |
| glDeleteSync(deletingThreadSyncObj); |
| ASSERT_GL_NO_ERROR(); |
| deletingThreadSyncObj = nullptr; |
| |
| // Draw using the shared texture. |
| drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f); |
| |
| continuingThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); |
| ASSERT_GL_NO_ERROR(); |
| // Force the fence to be created |
| glFlush(); |
| |
| // Wait for the other thread to delete its framebuffer. |
| threadSynchronization.nextStep(Step::Thread1Draw); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Delete)); |
| |
| // Write to the shared texture differently, so a dependency is created from the previous |
| // readers of the shared texture (the two framebuffers of the two threads) to the new |
| // commands being recorded for the shared texture. |
| // |
| // If the backend attempts to create a dependency from nodes associated with the |
| // previous readers of the texture to the new node that will contain the following |
| // commands, there will be a use-after-free error. |
| const GLColor kTexData2 = GLColor::green; |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| &kTexData2); |
| drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f); |
| |
| threadSynchronization.nextStep(Step::Finish); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| EXPECT_EGL_SUCCESS(); |
| }); |
| |
| deletingThread.join(); |
| continuingThread.join(); |
| |
| ASSERT_NE(currentStep, Step::Abort); |
| |
| // Clean up |
| for (size_t t = 0; t < kThreadCount; ++t) |
| { |
| eglDestroySurface(dpy, surface[t]); |
| eglDestroyContext(dpy, ctx[t]); |
| } |
| } |
| |
| // Tests that Context will be destroyed in thread cleanup callback and it is safe to call GLES APIs. |
| TEST_P(EGLContextSharingTest, ThreadCleanupCallback) |
| { |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); |
| ANGLE_SKIP_TEST_IF(!IsAndroid()); |
| |
| EGLWindow *window = getEGLWindow(); |
| EGLDisplay dpy = window->getDisplay(); |
| EGLConfig config = window->getConfig(); |
| |
| EGLContext context = window->createContext(EGL_NO_CONTEXT, nullptr); |
| EXPECT_NE(context, EGL_NO_CONTEXT); |
| |
| EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; |
| EGLSurface surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes); |
| EXPECT_NE(surface, EGL_NO_SURFACE); |
| |
| auto registerThreadCleanupCallback = []() { |
| #if defined(ANGLE_PLATFORM_ANDROID) |
| static pthread_once_t once = PTHREAD_ONCE_INIT; |
| static TLSIndex tlsIndex = TLS_INVALID_INDEX; |
| |
| auto createThreadCleanupTLSIndex = []() { |
| auto threadCleanupCallback = [](void *) { |
| // From the point of view of this test Context is still current. |
| glFinish(); |
| // Test expects that Context will be destroyed here. |
| eglReleaseThread(); |
| }; |
| tlsIndex = CreateTLSIndex(threadCleanupCallback); |
| }; |
| pthread_once(&once, createThreadCleanupTLSIndex); |
| ASSERT(tlsIndex != TLS_INVALID_INDEX); |
| |
| // Set any non nullptr value. |
| SetTLSValue(tlsIndex, &once); |
| #endif |
| }; |
| |
| std::thread thread([&]() { |
| registerThreadCleanupCallback(); |
| |
| eglMakeCurrent(dpy, surface, surface, context); |
| ASSERT_EGL_SUCCESS(); |
| |
| // Clear and read back to make sure thread uses context. |
| glClearColor(1.0, 0.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); |
| |
| // Destroy context and surface while still current. |
| eglDestroyContext(dpy, context); |
| ASSERT_EGL_SUCCESS(); |
| eglDestroySurface(dpy, surface); |
| ASSERT_EGL_SUCCESS(); |
| }); |
| |
| thread.join(); |
| |
| // Check if context was actually destroyed. |
| EGLint val; |
| EXPECT_EGL_FALSE(eglQueryContext(dpy, context, EGL_CONTEXT_CLIENT_TYPE, &val)); |
| EXPECT_EQ(eglGetError(), EGL_BAD_CONTEXT); |
| } |
| |
| // Tests that Context will be automatically unmade from current on thread exit. |
| TEST_P(EGLContextSharingTest, UnmakeFromCurrentOnThreadExit) |
| { |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); |
| ANGLE_SKIP_TEST_IF(!IsAndroid()); |
| |
| EGLWindow *window = getEGLWindow(); |
| EGLDisplay dpy = window->getDisplay(); |
| EGLConfig config = window->getConfig(); |
| |
| EGLContext context = window->createContext(EGL_NO_CONTEXT, nullptr); |
| EXPECT_NE(context, EGL_NO_CONTEXT); |
| |
| EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; |
| EGLSurface surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes); |
| EXPECT_NE(surface, EGL_NO_SURFACE); |
| |
| std::mutex mutex; |
| std::condition_variable condVar; |
| enum class Step |
| { |
| Start, |
| ThreadMakeCurrent, |
| Finish, |
| Abort, |
| }; |
| Step currentStep = Step::Start; |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| std::thread thread([&]() { |
| eglMakeCurrent(dpy, surface, surface, context); |
| ASSERT_EGL_SUCCESS(); |
| |
| threadSynchronization.nextStep(Step::ThreadMakeCurrent); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| |
| // Clear and read back to make sure thread uses context. |
| glClearColor(1.0, 0.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); |
| }); |
| |
| // Wait while Context is made current in the thread. |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::ThreadMakeCurrent)); |
| |
| // This should fail because context is already current in other thread. |
| eglMakeCurrent(dpy, surface, surface, context); |
| EXPECT_EQ(eglGetError(), EGL_BAD_ACCESS); |
| |
| // Finish the thread. |
| threadSynchronization.nextStep(Step::Finish); |
| thread.join(); |
| |
| // This should succeed, because thread is finished so context is no longer current. |
| eglMakeCurrent(dpy, surface, surface, context); |
| EXPECT_EGL_SUCCESS(); |
| eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| ASSERT_EGL_SUCCESS(); |
| |
| // Destroy context and surface. |
| eglDestroyContext(dpy, context); |
| ASSERT_EGL_SUCCESS(); |
| eglDestroySurface(dpy, surface); |
| ASSERT_EGL_SUCCESS(); |
| } |
| |
| // Test that an inactive but alive thread doesn't prevent memory cleanup. |
| TEST_P(EGLContextSharingTestNoFixture, InactiveThreadDoesntPreventCleanup) |
| { |
| EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), |
| EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, GetParam().getDeviceType(), |
| EGL_NONE}; |
| |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. |
| std::mutex mutex; |
| std::condition_variable condVar; |
| |
| enum class Step |
| { |
| Start, |
| Thread0Initialize, |
| Thread1MakeCurrent, |
| Thread0MakeCurrent, |
| Thread1Render, |
| Thread0Terminate, |
| Finish, |
| Abort, |
| }; |
| Step currentStep = Step::Start; |
| |
| std::thread thread0 = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); |
| |
| mDisplay = eglGetPlatformDisplayEXT( |
| EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| |
| threadSynchronization.nextStep(Step::Thread0Initialize); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1MakeCurrent)); |
| |
| EGLContext ctx; |
| EGLSurface srf; |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| EXPECT_TRUE(createContext(config, &ctx)); |
| |
| EXPECT_TRUE(createPbufferSurface(mDisplay, config, 1280, 720, &srf)); |
| ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, srf, srf, ctx)); |
| threadSynchronization.nextStep(Step::Thread0MakeCurrent); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Render)); |
| |
| eglTerminate(mDisplay); |
| EXPECT_EGL_SUCCESS(); |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| threadSynchronization.nextStep(Step::Thread0Terminate); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| |
| // Wait a little to simulate an inactive but alive thread. |
| angle::Sleep(100); |
| }); |
| |
| std::thread thread1 = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Initialize)); |
| |
| EGLContext ctx; |
| EGLSurface srf; |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| EXPECT_TRUE(createContext(config, &ctx)); |
| |
| EXPECT_TRUE(createPbufferSurface(mDisplay, config, 1280, 720, &srf)); |
| ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, srf, srf, ctx)); |
| |
| threadSynchronization.nextStep(Step::Thread1MakeCurrent); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0MakeCurrent)); |
| |
| // Clear and read back to make sure thread uses context and surface. |
| glClearColor(1.0, 0.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); |
| |
| threadSynchronization.nextStep(Step::Thread1Render); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Terminate)); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| threadSynchronization.nextStep(Step::Finish); |
| }); |
| |
| thread1.join(); |
| thread0.join(); |
| |
| ASSERT_NE(currentStep, Step::Abort); |
| } |
| |
| // Test that eglTerminate() with a thread doesn't cause other threads to crash. |
| TEST_P(EGLContextSharingTestNoFixture, EglTerminateMultiThreaded) |
| { |
| // http://anglebug.com/42264731 |
| // The following EGL calls led to a crash in eglMakeCurrent(): |
| // |
| // Thread A: eglMakeCurrent(context A) |
| // Thread B: eglDestroyContext(context A) |
| // B: eglTerminate() <<--- this release context A |
| // Thread A: eglMakeCurrent(context B) |
| |
| EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE}; |
| mDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| |
| mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); |
| EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); |
| ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed."; |
| |
| EXPECT_TRUE(createContext(config, &mContexts[0])); |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0])); |
| |
| // Must be after the eglMakeCurrent() so renderer string is initialized. |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); |
| // TODO(http://anglebug.com/42264822): Fails with OpenGL ES backend. |
| ANGLE_SKIP_TEST_IF(IsOpenGLES()); |
| |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. |
| std::mutex mutex; |
| std::condition_variable condVar; |
| |
| enum class Step |
| { |
| Start, |
| Thread0Clear, |
| Thread1Terminate, |
| Thread0MakeCurrentContext1, |
| Finish, |
| Abort, |
| }; |
| Step currentStep = Step::Start; |
| |
| std::thread thread0 = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0])); |
| |
| // Clear and read back to make sure thread 0 uses context 0. |
| glClearColor(1.0, 0.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); |
| |
| // Wait for thread 1 to clear. |
| threadSynchronization.nextStep(Step::Thread0Clear); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Terminate)); |
| |
| // First Display was terminated, so we need to create a new one to create a new Context. |
| mDisplay = eglGetPlatformDisplayEXT( |
| EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| EXPECT_TRUE(createContext(config, &mContexts[1])); |
| |
| // Thread1's terminate call will make mSurface an invalid handle, recreate a new surface |
| EXPECT_TRUE(createPbufferSurface(mDisplay, config, 1280, 720, &mSurface)); |
| ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[1])); |
| |
| // Clear and read back to make sure thread 0 uses context 1. |
| glClearColor(1.0, 1.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 255, 0, 255); |
| |
| // Cleanup |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[1])); |
| eglDestroySurface(mDisplay, mSurface); |
| mSurface = EGL_NO_SURFACE; |
| eglTerminate(mDisplay); |
| mDisplay = EGL_NO_DISPLAY; |
| EXPECT_EGL_SUCCESS(); |
| |
| threadSynchronization.nextStep(Step::Thread0MakeCurrentContext1); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| }); |
| |
| std::thread thread1 = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| // Wait for thread 0 to clear. |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear)); |
| |
| // Destroy context 0 while thread1 has it current. |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[0])); |
| EXPECT_EGL_SUCCESS(); |
| eglTerminate(mDisplay); |
| mDisplay = EGL_NO_DISPLAY; |
| EXPECT_EGL_SUCCESS(); |
| |
| // Wait for the thread 0 to make a new context and glClear(). |
| threadSynchronization.nextStep(Step::Thread1Terminate); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0MakeCurrentContext1)); |
| |
| threadSynchronization.nextStep(Step::Finish); |
| }); |
| |
| thread0.join(); |
| thread1.join(); |
| |
| ASSERT_NE(currentStep, Step::Abort); |
| } |
| |
| // Test that eglDestoryContext() can be called multiple times on the same Context without causing |
| // errors. |
| TEST_P(EGLContextSharingTestNoFixture, EglDestoryContextManyTimesSameContext) |
| { |
| EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE}; |
| mDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| |
| mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); |
| EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); |
| ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed."; |
| |
| EXPECT_TRUE(createContext(config, &mContexts[0])); |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0])); |
| |
| // Must be after the eglMakeCurrent() so renderer string is initialized. |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); |
| // TODO(http://anglebug.com/42264822): Fails with OpenGL ES backend. |
| ANGLE_SKIP_TEST_IF(IsOpenGLES()); |
| |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. |
| std::mutex mutex; |
| std::condition_variable condVar; |
| |
| enum class Step |
| { |
| Start, |
| Thread0Clear, |
| Thread1Terminate, |
| Thread0MakeCurrentContext1, |
| Finish, |
| Abort, |
| }; |
| Step currentStep = Step::Start; |
| |
| std::thread thread0 = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0])); |
| |
| // Clear and read back to make sure thread 0 uses context 0. |
| glClearColor(1.0, 0.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); |
| |
| // Wait for thread 1 to clear. |
| threadSynchronization.nextStep(Step::Thread0Clear); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Terminate)); |
| |
| // First Display was terminated, so we need to create a new one to create a new Context. |
| mDisplay = eglGetPlatformDisplayEXT( |
| EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| EXPECT_TRUE(createContext(config, &mContexts[1])); |
| |
| // Thread1's terminate call will make mSurface an invalid handle, recreate a new surface |
| EXPECT_TRUE(createPbufferSurface(mDisplay, config, 1280, 720, &mSurface)); |
| ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[1])); |
| EXPECT_EGL_SUCCESS(); |
| |
| // Clear and read back to make sure thread 0 uses context 1. |
| glClearColor(1.0, 1.0, 0.0, 1.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_PIXEL_EQ(0, 0, 255, 255, 0, 255); |
| |
| // Cleanup |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[1])); |
| eglDestroySurface(mDisplay, mSurface); |
| mSurface = EGL_NO_SURFACE; |
| eglTerminate(mDisplay); |
| mDisplay = EGL_NO_DISPLAY; |
| EXPECT_EGL_SUCCESS(); |
| |
| threadSynchronization.nextStep(Step::Thread0MakeCurrentContext1); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| }); |
| |
| std::thread thread1 = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| // Wait for thread 0 to clear. |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear)); |
| |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| |
| // Destroy context 0 5 times while thread1 has it current. |
| EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0])); |
| EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0])); |
| EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0])); |
| EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0])); |
| EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0])); |
| mContexts[0] = EGL_NO_CONTEXT; |
| |
| eglDestroySurface(mDisplay, mSurface); |
| mSurface = EGL_NO_SURFACE; |
| eglTerminate(mDisplay); |
| mDisplay = EGL_NO_DISPLAY; |
| EXPECT_EGL_SUCCESS(); |
| |
| // Wait for the thread 0 to make a new context and glClear(). |
| threadSynchronization.nextStep(Step::Thread1Terminate); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0MakeCurrentContext1)); |
| |
| threadSynchronization.nextStep(Step::Finish); |
| }); |
| |
| thread0.join(); |
| thread1.join(); |
| |
| ASSERT_NE(currentStep, Step::Abort); |
| } |
| |
| // Test that eglTerminate() can be called multiple times on the same Display while Contexts are |
| // still current without causing errors. |
| TEST_P(EGLContextSharingTestNoFixture, EglTerminateMultipleTimes) |
| { |
| // https://bugs.chromium.org/p/skia/issues/detail?id=12413#c4 |
| // The following sequence caused a crash with the D3D backend in the Skia infra: |
| // eglDestroyContext(ctx0) |
| // eglDestroySurface(srf0) |
| // eglTerminate(shared-display) |
| // eglDestroyContext(ctx1) // completes the cleanup from the above terminate |
| // eglDestroySurface(srf1) |
| // eglTerminate(shared-display) |
| |
| EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE}; |
| mDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| |
| mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); |
| EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); |
| EXPECT_TRUE(mSurface != EGL_NO_SURFACE); |
| ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed."; |
| |
| EXPECT_TRUE(createContext(config, &mContexts[0])); |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0])); |
| EXPECT_TRUE(createContext(config, &mContexts[1])); |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[1])); |
| |
| // Must be after the eglMakeCurrent() so renderer string is initialized. |
| // TODO(http://anglebug.com/42264822): Fails with Mac + OpenGL backend. |
| ANGLE_SKIP_TEST_IF(IsMac() && IsOpenGL()); |
| |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| |
| eglDestroySurface(mDisplay, mSurface); |
| mSurface = EGL_NO_SURFACE; |
| EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0])); |
| mContexts[0] = EGL_NO_CONTEXT; |
| eglTerminate(mDisplay); |
| EXPECT_EGL_SUCCESS(); |
| |
| eglDestroyContext(mDisplay, mContexts[1]); |
| mContexts[1] = EGL_NO_CONTEXT; |
| ASSERT_EGL_ERROR(EGL_NOT_INITIALIZED); |
| eglTerminate(mDisplay); |
| EXPECT_EGL_SUCCESS(); |
| mDisplay = EGL_NO_DISPLAY; |
| } |
| |
| // Test that we can eglSwapBuffers in one thread while another thread renders to a texture. |
| TEST_P(EGLContextSharingTestNoFixture, SwapBuffersShared) |
| { |
| EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE}; |
| mDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| |
| mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); |
| EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); |
| ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed."; |
| |
| EGLSurface pbufferSurface; |
| EXPECT_TRUE(createPbufferSurface(mDisplay, config, kWidth, kHeight, &pbufferSurface)); |
| |
| // Create the two contextss |
| EXPECT_TRUE(createContext(config, &mContexts[0])); |
| EXPECT_TRUE(createContext(config, &mContexts[1], mContexts[0])); |
| |
| eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]); |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); |
| eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. |
| std::mutex mutex; |
| std::condition_variable condVar; |
| |
| enum class Step |
| { |
| Start, |
| TextureInitialized, |
| Finish, |
| Abort, |
| }; |
| |
| Step currentStep = Step::Start; |
| |
| // Sample a texture in the swap thread. |
| std::thread swapThread = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); |
| eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]); |
| |
| glGenTextures(1, &mTexture); |
| glBindTexture(GL_TEXTURE_2D, mTexture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| nullptr); |
| |
| threadSynchronization.nextStep(Step::TextureInitialized); |
| |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(program); |
| |
| for (int i = 0; i < 100; ++i) |
| { |
| glClear(GL_COLOR_BUFFER_BIT); |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_GL_NO_ERROR(); |
| eglSwapBuffers(mDisplay, mSurface); |
| } |
| eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| eglReleaseThread(); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| }); |
| |
| // Render to the texture in the render thread. |
| std::thread renderThread = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| ASSERT_EGL_TRUE(eglMakeCurrent(mDisplay, pbufferSurface, pbufferSurface, mContexts[1])); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| |
| ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); |
| ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); |
| ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue()); |
| |
| // The render thread will draw to the texture. |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::TextureInitialized)); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0); |
| glDisable(GL_DEPTH_TEST); |
| |
| for (int i = 0; i < 400; ++i) |
| { |
| glClear(GL_COLOR_BUFFER_BIT); |
| glUseProgram(redProgram); |
| drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| |
| glClear(GL_COLOR_BUFFER_BIT); |
| glUseProgram(greenProgram); |
| drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| |
| glClear(GL_COLOR_BUFFER_BIT); |
| glUseProgram(blueProgram); |
| drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| } |
| eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| eglReleaseThread(); |
| |
| threadSynchronization.nextStep(Step::Finish); |
| }); |
| |
| swapThread.join(); |
| renderThread.join(); |
| |
| eglDestroySurface(mDisplay, pbufferSurface); |
| ASSERT_EGL_SUCCESS(); |
| } |
| |
| class EGLContextSharingTestNoSyncTextureUploads : public EGLContextSharingTest |
| {}; |
| |
| // Test that an application that does not synchronize when using textures across shared contexts can |
| // still see texture updates. This behavior is not required by the GLES specification, but is |
| // exhibited by some applications. That application will malfunction if our implementation does not |
| // handle this in the way it expects. Only the vulkan backend has the workaround needed for this |
| // usecase. |
| TEST_P(EGLContextSharingTestNoSyncTextureUploads, NoSync) |
| { |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| EGLConfig config = getEGLWindow()->getConfig(); |
| |
| const EGLint inShareGroupContextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; |
| const EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; |
| |
| constexpr size_t kThreadCount = 2; |
| EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE}; |
| |
| for (size_t t = 0; t < kThreadCount; ++t) |
| { |
| mContexts[t] = eglCreateContext(display, config, t == 0 ? EGL_NO_CONTEXT : mContexts[0], |
| inShareGroupContextAttribs); |
| ASSERT_EGL_SUCCESS(); |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[t]); |
| |
| surface[t] = eglCreatePbufferSurface(display, config, pbufferAttributes); |
| EXPECT_EGL_SUCCESS(); |
| ASSERT_NE(EGL_NO_SURFACE, surface[t]); |
| } |
| |
| GLTexture textureFromCtx0; |
| constexpr size_t kTextureCount = 10; |
| GLTexture textures[kTextureCount]; |
| |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. |
| std::mutex mutex; |
| std::condition_variable condVar; |
| enum class Step |
| { |
| Start, |
| Ctx0Current, |
| Ctx1Current, |
| TexturesDone, |
| Finish, |
| Abort, |
| }; |
| Step currentStep = Step::Start; |
| |
| std::thread creatingThread = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface[0], surface[0], mContexts[0])); |
| ASSERT_EGL_SUCCESS(); |
| threadSynchronization.nextStep(Step::Ctx0Current); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Ctx1Current)); |
| |
| // Create the shared textures that will be accessed by the other context |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx0); |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red); |
| glFinish(); |
| |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::blue); |
| // Do not glFinish |
| |
| // We set 6 to be the threshold to flush texture updates. |
| // We create redundant textures here to ensure that we trigger that threshold. |
| for (size_t i = 0; i < kTextureCount; i++) |
| { |
| glBindTexture(GL_TEXTURE_2D, textures[i]); |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| |
| ASSERT_GL_TRUE(glIsTexture(textures[i])); |
| |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, |
| &GLColor::blue); |
| } |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| threadSynchronization.nextStep(Step::TexturesDone); |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| ASSERT_EGL_SUCCESS(); |
| }); |
| |
| std::thread samplingThread = std::thread([&]() { |
| ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::Ctx0Current)); |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface[1], surface[1], mContexts[1])); |
| ASSERT_EGL_SUCCESS(); |
| threadSynchronization.nextStep(Step::Ctx1Current); |
| |
| ASSERT_TRUE(threadSynchronization.waitForStep(Step::TexturesDone)); |
| |
| ASSERT_GL_TRUE(glIsTexture(textureFromCtx0)); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Draw using ctx0 texture as sampler |
| GLTexture ctx1tex; |
| glBindTexture(GL_TEXTURE_2D, ctx1tex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| GLColor::black.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ctx1tex, 0); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLuint sampler; |
| glGenSamplers(1, &sampler); |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(program); |
| glActiveTexture(GL_TEXTURE0); |
| glBindTexture(GL_TEXTURE_2D, textureFromCtx0); |
| glBindSampler(0, sampler); |
| glUniform1i(glGetUniformLocation(program, essl1_shaders::PositionAttrib()), 0); |
| ASSERT_GL_NO_ERROR(); |
| |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue); |
| |
| threadSynchronization.nextStep(Step::Finish); |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); |
| ASSERT_EGL_SUCCESS(); |
| }); |
| |
| creatingThread.join(); |
| samplingThread.join(); |
| |
| ASSERT_NE(currentStep, Step::Abort); |
| ASSERT_EGL_SUCCESS(); |
| |
| for (size_t t = 0; t < kThreadCount; ++t) |
| { |
| ASSERT_EGL_TRUE(eglDestroySurface(display, surface[t])); |
| ASSERT_EGL_SUCCESS(); |
| } |
| } |
| |
| // Tests that creating a context and immediately destroying it works when no surface has been |
| // created. |
| TEST_P(EGLContextSharingTestNoFixture, ImmediateContextDestroyAfterCreation) |
| { |
| EGLAttrib dispattrs[3] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE}; |
| mDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); |
| |
| EGLConfig config = EGL_NO_CONFIG_KHR; |
| EXPECT_TRUE(chooseConfig(&config)); |
| |
| // Create a context and immediately destroy it. Note that no window surface should be created |
| // for this test. Regression test for platforms that expose multiple queue families in Vulkan, |
| // and ANGLE defers creation of the device until a surface is created. In this case, the |
| // context is being destroyed before a queue is ever created. |
| EXPECT_TRUE(createContext(config, &mContexts[0])); |
| EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[0])); |
| ASSERT_EGL_SUCCESS(); |
| } |
| } // anonymous namespace |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextSharingTest); |
| ANGLE_INSTANTIATE_TEST(EGLContextSharingTest, |
| ES2_D3D9(), |
| ES2_D3D11(), |
| ES3_D3D11(), |
| ES2_METAL(), |
| ES3_METAL(), |
| ES2_OPENGL(), |
| ES3_OPENGL(), |
| ES2_VULKAN(), |
| ES3_VULKAN()); |
| |
| ANGLE_INSTANTIATE_TEST(EGLContextSharingTestNoFixture, |
| WithNoFixture(ES2_METAL()), |
| WithNoFixture(ES3_METAL()), |
| WithNoFixture(ES2_OPENGLES()), |
| WithNoFixture(ES3_OPENGLES()), |
| WithNoFixture(ES2_OPENGL()), |
| WithNoFixture(ES3_OPENGL()), |
| WithNoFixture(ES2_VULKAN()), |
| WithNoFixture(ES3_VULKAN())); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextSharingTestNoSyncTextureUploads); |
| ANGLE_INSTANTIATE_TEST(EGLContextSharingTestNoSyncTextureUploads, |
| ES2_VULKAN().enable(Feature::ForceSubmitImmutableTextureUpdates), |
| ES3_VULKAN().enable(Feature::ForceSubmitImmutableTextureUpdates)); |