| // | |
| // 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/unsafe_buffers.h" | |
| #include <array> | |
| #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(); | |
| } | |
| bool chooseConfig(EGLDisplay dpy, EGLConfig *config) const | |
| { | |
| bool result = false; | |
| EGLint count = 0; | |
| EGLint clientVersion = | |
| GetParam().majorVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT; | |
| EGLint attribs[] = {EGL_RED_SIZE, | |
| 8, | |
| EGL_GREEN_SIZE, | |
| 8, | |
| EGL_BLUE_SIZE, | |
| 8, | |
| EGL_RENDERABLE_TYPE, | |
| clientVersion, | |
| EGL_SURFACE_TYPE, | |
| EGL_WINDOW_BIT | EGL_PBUFFER_BIT, | |
| EGL_NONE}; | |
| result = eglChooseConfig(dpy, attribs, config, 1, &count); | |
| EXPECT_EGL_TRUE(result && (count > 0)); | |
| return result; | |
| } | |
| std::array<EGLContext, 2> mContexts = {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 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 that destroying a context with an undeleted sampler does not cause use-after-free. | |
| // https://crbug.com/550347790 | |
| TEST_P(EGLContextSharingTest, DestroyContextWithUndeletedSampler) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); | |
| EGLDisplay display = getEGLWindow()->getDisplay(); | |
| EGLConfig config = getEGLWindow()->getConfig(); | |
| EGLSurface surface = getEGLWindow()->getSurface(); | |
| const bool hasVirt = IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_context_virtualization"); | |
| const EGLint virtAttribs1[] = {EGL_CONTEXT_CLIENT_VERSION, 3, | |
| EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, 1, EGL_NONE}; | |
| const EGLint virtAttribs2[] = {EGL_CONTEXT_CLIENT_VERSION, 3, | |
| EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, 2, EGL_NONE}; | |
| const EGLint plainAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; | |
| const EGLint *a1 = hasVirt ? virtAttribs1 : plainAttribs; | |
| const EGLint *a2 = hasVirt ? virtAttribs2 : plainAttribs; | |
| mContexts[0] = eglCreateContext(display, config, EGL_NO_CONTEXT, a1); | |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]); | |
| mContexts[1] = eglCreateContext(display, config, mContexts[0], a2); | |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]); | |
| // Context 0: compile a program (which may hold a ref to the renderer) and create a sampler. | |
| // Intentionally do NOT delete the sampler to simulate garbage collection / leaked objects. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); | |
| ASSERT_EGL_SUCCESS(); | |
| constexpr char kVS[] = R"(#version 300 es | |
| void main() { | |
| gl_Position = vec4(0.0); | |
| })"; | |
| constexpr char kFS[] = R"(#version 300 es | |
| precision mediump float; | |
| out vec4 color; | |
| void main() { | |
| color = vec4(1.0); | |
| })"; | |
| GLuint program = CompileProgram(kVS, kFS); | |
| ASSERT_NE(0u, program); | |
| GLSampler leakedSampler; | |
| glSamplerParameteri(leakedSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glBindSampler(0, leakedSampler); | |
| ASSERT_GL_NO_ERROR(); | |
| // Context 1: create and delete a sampler cleanly. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1])); | |
| ASSERT_EGL_SUCCESS(); | |
| GLSampler sampler2; | |
| sampler2.reset(); | |
| ASSERT_GL_NO_ERROR(); | |
| // Destroy context 0 without deleting leakedSampler. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); | |
| glBindSampler(0, 0); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| ASSERT_EGL_TRUE(SafeDestroyContext(display, mContexts[0])); | |
| // Clean up context 1. | |
| ASSERT_EGL_TRUE(SafeDestroyContext(display, mContexts[1])); | |
| } | |
| // Tests that destroying a single context with a program and an undeleted sampler does not cause | |
| // use-after-free. | |
| // https://crbug.com/550347790 | |
| TEST_P(EGLContextSharingTest, DestroySingleContextWithUndeletedSampler) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); | |
| EGLDisplay display = getEGLWindow()->getDisplay(); | |
| EGLConfig config = getEGLWindow()->getConfig(); | |
| EGLSurface surface = getEGLWindow()->getSurface(); | |
| const bool hasVirt = IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_context_virtualization"); | |
| const EGLint virtAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, | |
| EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, 1, EGL_NONE}; | |
| const EGLint plainAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; | |
| const EGLint *attribs = hasVirt ? virtAttribs : plainAttribs; | |
| mContexts[0] = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs); | |
| ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0])); | |
| ASSERT_EGL_SUCCESS(); | |
| constexpr char kVS[] = R"(#version 300 es | |
| void main() { | |
| gl_Position = vec4(0.0); | |
| })"; | |
| constexpr char kFS[] = R"(#version 300 es | |
| precision mediump float; | |
| out vec4 color; | |
| void main() { | |
| color = vec4(1.0); | |
| })"; | |
| GLuint program = CompileProgram(kVS, kFS); | |
| ASSERT_NE(0u, program); | |
| GLSampler leakedSampler; | |
| glSamplerParameteri(leakedSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glBindSampler(0, leakedSampler); | |
| ASSERT_GL_NO_ERROR(); | |
| glBindSampler(0, 0); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| ASSERT_EGL_TRUE(SafeDestroyContext(display, mContexts[0])); | |
| } | |
| // 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); | |
| } | |
| // Regression test for sampler lifetime bug when EGL_ANGLE_display_texture_share_group is used. | |
| TEST_P(EGLContextSharingTest, DisplayShareGroupSamplerInFlightWhenOwningShareGroupDestroyed) | |
| { | |
| EGLDisplay display = getEGLWindow()->getDisplay(); | |
| ANGLE_SKIP_TEST_IF( | |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group")); | |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); | |
| 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}; | |
| // Two contexts in the *display* texture share group but each in its own share group | |
| EGLContext contextA = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); | |
| EGLContext contextB = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); | |
| ASSERT_NE(contextA, EGL_NO_CONTEXT); | |
| ASSERT_NE(contextB, EGL_NO_CONTEXT); | |
| // A larger render target widens the GPU window for the heavy draw in B. | |
| constexpr int kHeavyDim = 256; | |
| const EGLint pbufferAttribs[] = {EGL_WIDTH, kHeavyDim, EGL_HEIGHT, kHeavyDim, EGL_NONE}; | |
| EGLSurface pbufferB = eglCreatePbufferSurface(display, config, pbufferAttribs); | |
| ASSERT_NE(pbufferB, EGL_NO_SURFACE); | |
| // Context A: create the shared texture and draw with it once. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, contextA)); | |
| GLTexture sharedTex; | |
| glBindTexture(GL_TEXTURE_2D, sharedTex); | |
| constexpr GLsizei kTexSize = 4; | |
| std::vector<GLColor> texData(kTexSize * kTexSize, GLColor::green); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, | |
| texData.data()); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| { | |
| ANGLE_GL_PROGRAM(progA, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); | |
| drawQuad(progA, essl1_shaders::PositionAttrib(), 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); | |
| } | |
| // Context B: bind the same display-shared texture and submit a long running draw. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, pbufferB, pbufferB, contextB)); | |
| glViewport(0, 0, kHeavyDim, kHeavyDim); | |
| glBindTexture(GL_TEXTURE_2D, sharedTex); | |
| constexpr char kHeavyFS[] = R"(precision highp float; | |
| varying vec2 v_texCoord; | |
| uniform sampler2D u_tex2D; | |
| void main() | |
| { | |
| vec4 acc = vec4(0.0); | |
| // Busy work to make sure the GPU is using the texture when context A is destroyed. | |
| for (int i = 0; i < 4000; ++i) | |
| { | |
| acc += texture2D(u_tex2D, v_texCoord + vec2(float(i) * 0.000001, 0.0)); | |
| } | |
| gl_FragColor = acc * 0.00025; | |
| })"; | |
| ANGLE_GL_PROGRAM(progHeavy, essl1_shaders::vs::Texture2D(), kHeavyFS); | |
| glUseProgram(progHeavy); | |
| glUniform1i(glGetUniformLocation(progHeavy, "u_tex2D"), 0); | |
| // Several expensive draw calls so the GPU is using the texture when context A is destroyed | |
| for (int i = 0; i < 16; ++i) | |
| { | |
| drawQuad(progHeavy, essl1_shaders::PositionAttrib(), 0.5f); | |
| } | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); // batch_B submitted, GPU now executing with S_A in its descriptor set. | |
| // Still in B: dirty a sampler-state bit and draw again. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); | |
| { | |
| ANGLE_GL_PROGRAM(progB2, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); | |
| drawQuad(progB2, essl1_shaders::PositionAttrib(), 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); | |
| } | |
| // Destroy context A. The texture's sampler currently in use by context B should not be freed. | |
| SafeDestroyContext(display, contextA); | |
| // Finish, to make sure the work is done. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, pbufferB, pbufferB, contextB)); | |
| glFinish(); | |
| sharedTex.reset(); | |
| EXPECT_GL_NO_ERROR(); | |
| eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| eglDestroySurface(display, pbufferB); | |
| SafeDestroyContext(display, contextB); | |
| getEGLWindow()->makeCurrent(); | |
| } | |
| // Variant of DisplayShareGroupSamplerInFlightWhenOwningShareGroupDestroyed, except the texture's | |
| // sampler state is not modified | |
| TEST_P(EGLContextSharingTest, DisplayShareGroupOrphanedSamplerReapedWhileInFlight) | |
| { | |
| EGLDisplay display = getEGLWindow()->getDisplay(); | |
| ANGLE_SKIP_TEST_IF( | |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group")); | |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); | |
| 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}; | |
| EGLContext contextA = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); | |
| EGLContext contextB = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); | |
| EGLContext contextC = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs); | |
| ASSERT_NE(contextA, EGL_NO_CONTEXT); | |
| ASSERT_NE(contextB, EGL_NO_CONTEXT); | |
| ASSERT_NE(contextC, EGL_NO_CONTEXT); | |
| constexpr int kHeavyDim = 256; | |
| const EGLint pbufferAttribs[] = {EGL_WIDTH, kHeavyDim, EGL_HEIGHT, kHeavyDim, EGL_NONE}; | |
| EGLSurface pbufferB = eglCreatePbufferSurface(display, config, pbufferAttribs); | |
| ASSERT_NE(pbufferB, EGL_NO_SURFACE); | |
| // A: Create texture and draw: this creates an internal sampler for the texture | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, contextA)); | |
| GLTexture sharedTex; | |
| glBindTexture(GL_TEXTURE_2D, sharedTex); | |
| std::vector<GLColor> texData(16, GLColor::cyan); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData.data()); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| { | |
| ANGLE_GL_PROGRAM(p, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); | |
| drawQuad(p, essl1_shaders::PositionAttrib(), 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); | |
| } | |
| // B: Draw with texture, using an expensive shader so the GPU is busy when context C is | |
| // destroyed | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, pbufferB, pbufferB, contextB)); | |
| glViewport(0, 0, kHeavyDim, kHeavyDim); | |
| glBindTexture(GL_TEXTURE_2D, sharedTex); | |
| constexpr char kHeavyFS[] = R"(precision highp float; | |
| varying vec2 v_texCoord; | |
| uniform sampler2D u_tex2D; | |
| void main() | |
| { | |
| vec4 acc = vec4(0.0); | |
| for (int i = 0; i < 4000; ++i) | |
| { | |
| acc += texture2D(u_tex2D, v_texCoord + vec2(float(i) * 0.000001, 0.0)); | |
| } | |
| gl_FragColor = acc * 0.00025; | |
| })"; | |
| ANGLE_GL_PROGRAM(progHeavy, essl1_shaders::vs::Texture2D(), kHeavyFS); | |
| glUseProgram(progHeavy); | |
| glUniform1i(glGetUniformLocation(progHeavy, "u_tex2D"), 0); | |
| for (int i = 0; i < 16; ++i) | |
| { | |
| drawQuad(progHeavy, essl1_shaders::PositionAttrib(), 0.5f); | |
| } | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); | |
| // Destroy A first; context B still references the texture's internal sampler. | |
| SafeDestroyContext(display, contextA); | |
| // B: dirty sampler state and draw so the texture's internal sampler is recreated. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, pbufferB, pbufferB, contextB)); | |
| glBindTexture(GL_TEXTURE_2D, sharedTex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); | |
| { | |
| ANGLE_GL_PROGRAM(p2, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); | |
| drawQuad(p2, essl1_shaders::PositionAttrib(), 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); | |
| } | |
| // Destroy unrelated context C. The original texture's internal sampler, still in use by the | |
| // GPU, should not be freed. | |
| SafeDestroyContext(display, contextC); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, pbufferB, pbufferB, contextB)); | |
| glFinish(); | |
| sharedTex.reset(); | |
| EXPECT_GL_NO_ERROR(); | |
| eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| eglDestroySurface(display, pbufferB); | |
| SafeDestroyContext(display, contextB); | |
| getEGLWindow()->makeCurrent(); | |
| } | |
| // 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 http://anglebug.com/42265935). | |
| 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; | |
| std::array<EGLSurface, kThreadCount> surface = {EGL_NO_SURFACE, EGL_NO_SURFACE}; | |
| std::array<EGLContext, kThreadCount> ctx = {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. | |
| std::array<GLRenderbuffer, kThreadCount> renderbuffer; | |
| std::array<GLFramebuffer, kThreadCount> fbo; | |
| std::array<GLProgram, kThreadCount> program; | |
| 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(); | |
| } | |
| // Tests that calling MakeCurrent() with EGL_NO_CONTEXT must also specify EGL_NO_SURFACE | |
| TEST_P(EGLContextSharingTest, MakeCurrentNoContextAndNoSurface) | |
| { | |
| EGLWindow *window = getEGLWindow(); | |
| // There's a valid context and surface current at the test start | |
| ASSERT_NE(eglGetCurrentContext(), EGL_NO_CONTEXT); | |
| ASSERT_EGL_SUCCESS(); | |
| // Release the current context. The helper must substitute EGL_NO_SURFACE for both the | |
| // draw and read surfaces, while using a real window surface would cause EGL_BAD_MATCH | |
| // and further downstream errors | |
| EXPECT_TRUE(window->makeCurrent(EGL_NO_CONTEXT)); | |
| EXPECT_EGL_SUCCESS(); | |
| // A failed eglMakeCurrent() will leave the previous context/surface bound | |
| EXPECT_EQ(eglGetCurrentContext(), EGL_NO_CONTEXT); | |
| EXPECT_EQ(eglGetCurrentSurface(EGL_DRAW), EGL_NO_SURFACE); | |
| EXPECT_EQ(eglGetCurrentSurface(EGL_READ), EGL_NO_SURFACE); | |
| EXPECT_TRUE(window->makeCurrent()); | |
| EXPECT_EGL_SUCCESS(); | |
| } | |
| // Test that an inactive but alive thread doesn't prevent memory cleanup. | |
| TEST_P(EGLContextSharingTestNoFixture, InactiveThreadDoesntPreventCleanup) | |
| { | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, | |
| GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(mOsWindow->getNativeDisplayPlatformType()), | |
| 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 = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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(mDisplay, &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) | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(GetPbufferOnlyDefaultPlatformType()), EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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 = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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) | |
| { | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(mOsWindow->getNativeDisplayPlatformType()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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 = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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) | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(mOsWindow->getNativeDisplayPlatformType()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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) | |
| { | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(mOsWindow->getNativeDisplayPlatformType()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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; | |
| std::array<EGLSurface, kThreadCount> surface = {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; | |
| std::array<GLTexture, kTextureCount> textures; | |
| // 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[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(mOsWindow->getNativeDisplayPlatformType()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| 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(mDisplay, &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(); | |
| } | |
| class EGLPriorityContextSharingTestNoFixture : public EGLContextSharingTest | |
| { | |
| public: | |
| EGLPriorityContextSharingTestNoFixture() : EGLContextSharingTest() {} | |
| void testSetUp() override | |
| { | |
| mMajorVersion = GetParam().majorVersion; | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(GetPbufferOnlyDefaultPlatformType()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); | |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); | |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); | |
| } | |
| void testTearDown() override | |
| { | |
| ASSERT_EGL_SUCCESS() << "Error during EGLPriorityContextSharingTestNoFixture TearDown"; | |
| } | |
| EGLDisplay mDisplay = EGL_NO_DISPLAY; | |
| const EGLint kWidth = 64; | |
| const EGLint kHeight = 64; | |
| EGLint mMajorVersion = 0; | |
| }; | |
| // Tests that create and destroy higher priority shared context | |
| TEST_P(EGLPriorityContextSharingTestNoFixture, MultiContextsCreateDestroy) | |
| { | |
| ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_IMG_context_priority")); | |
| EGLConfig config = EGL_NO_CONFIG_KHR; | |
| EXPECT_TRUE(chooseConfig(mDisplay, &config)); | |
| // Initialize contexts | |
| constexpr size_t kContextCount = 2; | |
| std::array<EGLSurface, kContextCount> surface = {EGL_NO_SURFACE, EGL_NO_SURFACE}; | |
| std::array<EGLContext, kContextCount> ctx = {EGL_NO_CONTEXT, EGL_NO_CONTEXT}; | |
| std::array<EGLint, kContextCount> priorities = {EGL_CONTEXT_PRIORITY_LOW_IMG, | |
| EGL_CONTEXT_PRIORITY_HIGH_IMG}; | |
| EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE}; | |
| EGLint attributes[] = {EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_NONE, EGL_NONE}; | |
| for (size_t t = 0; t < kContextCount; ++t) | |
| { | |
| surface[t] = eglCreatePbufferSurface(mDisplay, config, pbufferAttributes); | |
| EXPECT_EGL_SUCCESS(); | |
| attributes[1] = priorities[t]; | |
| ctx[t] = eglCreateContext(mDisplay, config, t == 0 ? EGL_NO_CONTEXT : ctx[0], attributes); | |
| EXPECT_NE(EGL_NO_CONTEXT, ctx[t]); | |
| EXPECT_EGL_SUCCESS(); | |
| eglMakeCurrent(mDisplay, surface[t], surface[t], ctx[t]); | |
| EXPECT_EGL_SUCCESS(); | |
| } | |
| eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| for (size_t t = 0; t < kContextCount; ++t) | |
| { | |
| eglDestroySurface(mDisplay, surface[t]); | |
| eglDestroyContext(mDisplay, ctx[t]); | |
| EXPECT_EGL_SUCCESS(); | |
| } | |
| eglTerminate(mDisplay); | |
| mDisplay = EGL_NO_DISPLAY; | |
| ASSERT_EGL_SUCCESS(); | |
| eglReleaseThread(); | |
| ASSERT_EGL_SUCCESS(); | |
| } | |
| } // anonymous namespace | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextSharingTest); | |
| ANGLE_INSTANTIATE_TEST(EGLContextSharingTest, | |
| 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)); | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLPriorityContextSharingTestNoFixture); | |
| ANGLE_INSTANTIATE_TEST(EGLPriorityContextSharingTestNoFixture, WithNoFixture(ES3_VULKAN())); |