| // | |
| // Copyright 2026 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. | |
| // | |
| // EGLBindTexImageTest.cpp: | |
| // Tests for eglBindTexImage | |
| // | |
| #include <gtest/gtest.h> | |
| #include <array> | |
| #include "common/unsafe_buffers.h" | |
| #include <iostream> | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/angle_test_configs.h" | |
| #include "test_utils/gl_raii.h" | |
| #include "util/test_utils.h" | |
| using namespace angle; | |
| enum class FlushMode | |
| { | |
| GlSyncImplicitSubmitEnabled, | |
| ExplicitGlFlushWithGlSyncImplicitSubmitDisabled, | |
| GlClientWaitFlagFlushWithGlSyncImplicitSubmitDisabled, | |
| GlSyncImplicitSubmitDisabled | |
| }; | |
| enum class ContextMode | |
| { | |
| SingleContext, | |
| MultiContext, | |
| }; | |
| using EGLBindTexImageTestParams = std::tuple<PlatformParameters, FlushMode, ContextMode>; | |
| std::string PrintBindTexImageTestParamsToString( | |
| const ::testing::TestParamInfo<EGLBindTexImageTestParams> &info) | |
| { | |
| PlatformParameters platformParams = std::get<0>(info.param); | |
| FlushMode flushMode = std::get<1>(info.param); | |
| ContextMode contextMode = std::get<2>(info.param); | |
| std::stringstream ss; | |
| ss << platformParams << "_"; | |
| switch (flushMode) | |
| { | |
| case FlushMode::GlSyncImplicitSubmitEnabled: | |
| ss << "_Implicit_Submit"; | |
| break; | |
| case FlushMode::ExplicitGlFlushWithGlSyncImplicitSubmitDisabled: | |
| ss << "_Explicit_Flush_No_Implicit_Submit"; | |
| break; | |
| case FlushMode::GlClientWaitFlagFlushWithGlSyncImplicitSubmitDisabled: | |
| ss << "_GlClientWait_Flag_No_Implicit_Submit"; | |
| break; | |
| case FlushMode::GlSyncImplicitSubmitDisabled: | |
| ss << "_No_Implicit_Submit"; | |
| break; | |
| } | |
| switch (contextMode) | |
| { | |
| case ContextMode::SingleContext: | |
| ss << "_Single_Context"; | |
| break; | |
| case ContextMode::MultiContext: | |
| ss << "_Multiple_Context"; | |
| break; | |
| } | |
| return ss.str(); | |
| } | |
| class EGLBindTexImageTest : public ANGLETest<EGLBindTexImageTestParams> | |
| { | |
| public: | |
| EGLBindTexImageTest() : mDisplay(EGL_NO_DISPLAY) {} | |
| void testSetUp() override | |
| { | |
| PlatformParameters platformParams = std::get<0>(GetParam()); | |
| mFlushMode = std::get<1>(GetParam()); | |
| mContextMode = std::get<2>(GetParam()); | |
| std::vector<const char *> enabledFeatureOverrides; | |
| switch (mFlushMode) | |
| { | |
| case FlushMode::GlSyncImplicitSubmitEnabled: | |
| // noop | |
| break; | |
| case FlushMode::ExplicitGlFlushWithGlSyncImplicitSubmitDisabled: | |
| case FlushMode::GlClientWaitFlagFlushWithGlSyncImplicitSubmitDisabled: | |
| case FlushMode::GlSyncImplicitSubmitDisabled: | |
| enabledFeatureOverrides.push_back( | |
| "disableSubmitCommandsOnSyncStatusCheckForTesting"); | |
| break; | |
| } | |
| enabledFeatureOverrides.push_back(nullptr); | |
| if (!IsEGLClientExtensionEnabled("EGL_ANGLE_feature_control")) | |
| { | |
| GTEST_SKIP() << "Test skipped because EGL_ANGLE_feature_control is not available."; | |
| } | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, | |
| platformParams.getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(GetPbufferOnlyDefaultPlatformType()), | |
| EGL_FEATURE_OVERRIDES_ENABLED_ANGLE, | |
| reinterpret_cast<EGLAttrib>(enabledFeatureOverrides.data()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); | |
| EXPECT_NE(mDisplay, EGL_NO_DISPLAY); | |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); | |
| chooseConfig(&mConfig); | |
| EXPECT_NE(mConfig, EGL_NO_CONFIG_KHR); | |
| createContext(mConfig, &mContexts[0]); | |
| createPbufferSurface(mConfig, &mSurfaces[0]); | |
| if (mContextMode == ContextMode::MultiContext) | |
| { | |
| createContext(mConfig, &mContexts[1]); | |
| createPbufferSurface(mConfig, &mSurfaces[1]); | |
| } | |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, mSurfaces[0], mSurfaces[0], mContexts[0])); | |
| EXPECT_EGL_SUCCESS() << "eglMakeCurrent failed."; | |
| } | |
| void testTearDown() override | |
| { | |
| if (mDisplay != EGL_NO_DISPLAY) | |
| { | |
| eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| for (size_t i = 0; i < 2; i++) | |
| { | |
| if (mSurfaces[i] != EGL_NO_SURFACE) | |
| { | |
| eglDestroySurface(mDisplay, mSurfaces[i]); | |
| } | |
| if (mContexts[i] != EGL_NO_CONTEXT) | |
| { | |
| eglDestroyContext(mDisplay, mContexts[i]); | |
| } | |
| } | |
| eglTerminate(mDisplay); | |
| eglReleaseThread(); | |
| mDisplay = EGL_NO_DISPLAY; | |
| } | |
| EXPECT_EGL_SUCCESS() << "Error during test TearDown"; | |
| } | |
| void chooseConfig(EGLConfig *config) | |
| { | |
| EGLint attribs[] = {EGL_RED_SIZE, | |
| 8, | |
| EGL_GREEN_SIZE, | |
| 8, | |
| EGL_BLUE_SIZE, | |
| 8, | |
| EGL_ALPHA_SIZE, | |
| 8, | |
| EGL_RENDERABLE_TYPE, | |
| EGL_OPENGL_ES3_BIT, | |
| EGL_SURFACE_TYPE, | |
| EGL_PBUFFER_BIT, | |
| EGL_BIND_TO_TEXTURE_RGBA, | |
| EGL_TRUE, | |
| EGL_NONE}; | |
| EGLint count = 0; | |
| EXPECT_TRUE(eglChooseConfig(mDisplay, attribs, config, 1, &count)); | |
| EXPECT_EGL_TRUE(count > 0); | |
| } | |
| void createContext(EGLConfig config, EGLContext *context) | |
| { | |
| ASSERT(context); | |
| EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; | |
| *context = eglCreateContext(mDisplay, config, nullptr, attribs); | |
| EXPECT_EGL_SUCCESS(); | |
| EXPECT_NE(*context, EGL_NO_CONTEXT); | |
| } | |
| void createPbufferSurface(EGLConfig config, EGLSurface *surface) | |
| { | |
| ASSERT(surface); | |
| EGLint attribs[] = {EGL_WIDTH, | |
| kWidth, | |
| EGL_HEIGHT, | |
| kHeight, | |
| EGL_TEXTURE_FORMAT, | |
| EGL_TEXTURE_RGBA, | |
| EGL_TEXTURE_TARGET, | |
| EGL_TEXTURE_2D, | |
| EGL_NONE}; | |
| *surface = eglCreatePbufferSurface(mDisplay, config, attribs); | |
| EXPECT_NE(*surface, EGL_NO_SURFACE); | |
| } | |
| EGLDisplay mDisplay = EGL_NO_DISPLAY; | |
| std::array<EGLContext, 2> mContexts = {EGL_NO_CONTEXT, EGL_NO_CONTEXT}; | |
| std::array<EGLContext, 2> mSurfaces = {EGL_NO_SURFACE, EGL_NO_SURFACE}; | |
| EGLConfig mConfig = EGL_NO_CONFIG_KHR; | |
| FlushMode mFlushMode; | |
| ContextMode mContextMode; | |
| static const EGLint kWidth = 16; | |
| static const EGLint kHeight = 16; | |
| }; | |
| // Test eglBindTexImage with different parameters | |
| TEST_P(EGLBindTexImageTest, Basic) | |
| { | |
| // Draw to mSurfaces[0] | |
| GLuint program = CompileProgram(essl3_shaders::vs::Passthrough(), essl3_shaders::fs::Red()); | |
| EXPECT_GL_NO_ERROR(); | |
| glUseProgram(program); | |
| EXPECT_GL_NO_ERROR(); | |
| drawQuad(program, "a_position", 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| GLsync sync; | |
| if (mContextMode == ContextMode::SingleContext) | |
| { | |
| sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| else | |
| { | |
| EXPECT_TRUE(eglMakeCurrent(mDisplay, mSurfaces[1], mSurfaces[1], mContexts[1])); | |
| EXPECT_EGL_SUCCESS() << "eglMakeCurrent failed."; | |
| } | |
| GLTexture tex; | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| EXPECT_GL_NO_ERROR(); | |
| // Bind mSurfaces[0] as texture | |
| eglBindTexImage(mDisplay, mSurfaces[0], EGL_BACK_BUFFER); | |
| EXPECT_EGL_SUCCESS(); | |
| if (mFlushMode == FlushMode::ExplicitGlFlushWithGlSyncImplicitSubmitDisabled) | |
| { | |
| glFlush(); | |
| } | |
| EXPECT_GL_NO_ERROR(); | |
| if (mContextMode == ContextMode::SingleContext) | |
| { | |
| // For single contexts, check that prior commands have been submitted by | |
| // creating and client waiting on a sync object and expecting to be unblocked. | |
| GLbitfield clientWaitSyncFlags = | |
| mFlushMode == FlushMode::GlClientWaitFlagFlushWithGlSyncImplicitSubmitDisabled | |
| ? GL_SYNC_FLUSH_COMMANDS_BIT | |
| : 0; | |
| constexpr GLuint64 kNanosPerSecond = 1000000000; | |
| EGLint syncStatus = glClientWaitSync(sync, clientWaitSyncFlags, kNanosPerSecond); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_TRUE(syncStatus == GL_CONDITION_SATISFIED || syncStatus == GL_ALREADY_SIGNALED); | |
| } | |
| else | |
| { | |
| // For multiple contexts, bind the texture to a framebuffer and read from that | |
| GLFramebuffer fb; | |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, fb); | |
| EXPECT_GL_NO_ERROR(); | |
| glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_TRUE(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(kWidth / 2, kHeight / 2, angle::GLColor::red); | |
| EXPECT_GL_NO_ERROR(); | |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| eglReleaseTexImage(mDisplay, mSurfaces[0], EGL_BACK_BUFFER); | |
| EXPECT_EGL_SUCCESS(); | |
| glBindTexture(GL_TEXTURE_2D, 0); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLBindTexImageTest); | |
| ANGLE_INSTANTIATE_TEST_COMBINE_2( | |
| EGLBindTexImageTest, | |
| PrintBindTexImageTestParamsToString, | |
| testing::Values(FlushMode::GlSyncImplicitSubmitEnabled, | |
| FlushMode::ExplicitGlFlushWithGlSyncImplicitSubmitDisabled, | |
| FlushMode::GlClientWaitFlagFlushWithGlSyncImplicitSubmitDisabled, | |
| FlushMode::GlSyncImplicitSubmitDisabled), | |
| testing::Values(ContextMode::SingleContext, ContextMode::MultiContext), | |
| WithNoFixture(ES3_VULKAN())); |