Implement EGL_ANGLE_context_virtualization on GLX Implement the EGL_ANGLE_context_virtualization extension that, despite its name, actually allows the de-virtualization of EGL contexts. Implement ContextGLX and RendererGLX classes to hold the native glx context per-renderer, rather than per-DisplayGLX as before. Where ContextGL and RendererGL were created, create these new subclasses instead. Remove the glx::Context and glx::Pbuffer from DisplayGLX in favour of the ones in RendererGLX instead. Factor a new DisplayGLX::createRenderer() out of DisplayGLX::initialize(). This creates a glx::Context and glx::Pbuffer for the new Renderer. Implement DisplayGLX::mVirtualizationGroups to store a map from virtualization group to Renderer. Add an EGLContext test that exercises eglMakeCurrent and eglGetCurrentContext on multiple threads. Add a parameter to EGLWindow to request EGL_PBUFFER_BIT. Some tests were silently relying on the backend choosing a config that supports pbuffers, but GLX does not. Modify the tests to explicitly request support Bug: angleproject:40096700, chromium:491346041 Change-Id: I71b309c44f48e956e67794daf2ba067bd9fbfbad Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7688012 Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Stephen White <senorblanco@chromium.org> Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/renderer/gl/gl_backend.gni b/src/libANGLE/renderer/gl/gl_backend.gni index c4801b5..1d89bf7 100644 --- a/src/libANGLE/renderer/gl/gl_backend.gni +++ b/src/libANGLE/renderer/gl/gl_backend.gni
@@ -95,6 +95,8 @@ if (angle_use_x11) { gl_backend_sources += [ + "glx/ContextGLX.cpp", + "glx/ContextGLX.h", "glx/DisplayGLX.cpp", "glx/DisplayGLX.h", "glx/DisplayGLX_api.h", @@ -104,6 +106,8 @@ "glx/PbufferSurfaceGLX.h", "glx/PixmapSurfaceGLX.cpp", "glx/PixmapSurfaceGLX.h", + "glx/RendererGLX.cpp", + "glx/RendererGLX.h", "glx/SurfaceGLX.h", "glx/WindowSurfaceGLX.cpp", "glx/WindowSurfaceGLX.h",
diff --git a/src/libANGLE/renderer/gl/glx/ContextGLX.cpp b/src/libANGLE/renderer/gl/glx/ContextGLX.cpp new file mode 100644 index 0000000..25773a7 --- /dev/null +++ b/src/libANGLE/renderer/gl/glx/ContextGLX.cpp
@@ -0,0 +1,32 @@ +// +// 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. +// + +#include "libANGLE/renderer/gl/glx/ContextGLX.h" +#include "libANGLE/renderer/gl/glx/RendererGLX.h" + +namespace rx +{ + +ContextGLX::ContextGLX(const gl::State &state, + gl::ErrorSet *errorSet, + const std::shared_ptr<RendererGLX> &renderer, + RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus) + : ContextGL(state, errorSet, renderer, robustnessVideoMemoryPurgeStatus), mRendererGLX(renderer) +{} + +ContextGLX::~ContextGLX() {} + +glx::Context ContextGLX::getContext() const +{ + return mRendererGLX->getContext(); +} + +glx::Pbuffer ContextGLX::getPbuffer() const +{ + return mRendererGLX->getPbuffer(); +} + +} // namespace rx
diff --git a/src/libANGLE/renderer/gl/glx/ContextGLX.h b/src/libANGLE/renderer/gl/glx/ContextGLX.h new file mode 100644 index 0000000..45ab942 --- /dev/null +++ b/src/libANGLE/renderer/gl/glx/ContextGLX.h
@@ -0,0 +1,38 @@ +// +// 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. +// + +// ContextGLX.h: Context class for GLX on Linux. + +#ifndef LIBANGLE_RENDERER_GL_GLX_CONTEXTGLX_H_ +#define LIBANGLE_RENDERER_GL_GLX_CONTEXTGLX_H_ + +#include "libANGLE/renderer/gl/ContextGL.h" +#include "libANGLE/renderer/gl/glx/FunctionsGLX.h" + +namespace rx +{ + +class RendererGLX; +struct ExternalContextState; + +class ContextGLX : public ContextGL +{ + public: + ContextGLX(const gl::State &state, + gl::ErrorSet *errorSet, + const std::shared_ptr<RendererGLX> &renderer, + RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus); + ~ContextGLX() override; + + glx::Context getContext() const; + glx::Pbuffer getPbuffer() const; + + private: + std::shared_ptr<RendererGLX> mRendererGLX; +}; +} // namespace rx + +#endif // LIBANGLE_RENDERER_GL_GLX_RENDERERGLX_H_
diff --git a/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp b/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp index 0846ba9..6338ca3 100644 --- a/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp +++ b/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp
@@ -20,7 +20,6 @@ #include "libANGLE/Context.h" #include "libANGLE/Display.h" #include "libANGLE/Surface.h" -#include "libANGLE/renderer/gl/ContextGL.h" #include "libANGLE/renderer/gl/RendererGL.h" #include "libANGLE/renderer/gl/renderergl_utils.h" @@ -28,6 +27,7 @@ #include <EGL/eglext.h> +#include "libANGLE/renderer/gl/glx/ContextGLX.h" #include "libANGLE/renderer/gl/glx/DisplayGLX_api.h" #include "libANGLE/renderer/gl/glx/PbufferSurfaceGLX.h" #include "libANGLE/renderer/gl/glx/PixmapSurfaceGLX.h" @@ -78,9 +78,7 @@ : DisplayGL(state), mRequestedVisual(-1), mContextConfig(nullptr), - mContext(nullptr), mCurrentNativeContexts(), - mInitPbuffer(0), mUsesNewXDisplay(false), mIsMesa(false), mHasMultisample(false), @@ -93,7 +91,6 @@ mMinSwapInterval(0), mMaxSwapInterval(0), mCurrentSwapInterval(-1), - mCurrentDrawable(0), mXDisplay(nullptr), mEGLDisplay(nullptr) {} @@ -229,10 +226,27 @@ XFree(candidates); } - const auto &eglAttributes = display->getAttributeMap(); + ANGLE_TRY(createRenderer(0, true, &mRenderer)); + ASSERT(mRenderer); + const gl::Version &maxVersion = mRenderer->getMaxSupportedESVersion(); + if (maxVersion < gl::Version(2, 0)) + { + return egl::Error(EGL_NOT_INITIALIZED, "OpenGL ES 2.0 is not supportable."); + } + + return DisplayGL::initialize(display); +} + +egl::Error DisplayGLX::createRenderer(glx::Context shareContext, + bool makeNewContextCurrent, + std::shared_ptr<RendererGLX> *outRenderer) +{ + glx::Context context = 0; + + const auto &eglAttributes = mEGLDisplay->getAttributeMap(); if (mHasARBCreateContext) { - egl::Error error = initializeContext(mContextConfig, eglAttributes, &mContext); + egl::Error error = initializeContext(shareContext, mContextConfig, eglAttributes, &context); if (error.isError()) { return error; @@ -262,17 +276,15 @@ } ASSERT(numVisuals == 1); - mContext = mGLX.createContext(&visuals[0], nullptr, true); + context = mGLX.createContext(&visuals[0], shareContext, true); XFree(visuals); - if (!mContext) + if (!context) { return egl::Error(EGL_NOT_INITIALIZED, "Could not create GL context."); } } - ASSERT(mContext); - - mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()] = mContext; + ASSERT(context); // FunctionsGL and DisplayGL need to make a few GL calls, for example to // query the version of the context so we need to make the context current. @@ -287,13 +299,13 @@ int initPbufferAttribs[] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None, }; - mInitPbuffer = mGLX.createPbuffer(mContextConfig, initPbufferAttribs); - if (!mInitPbuffer) + glx::Pbuffer pbuffer = mGLX.createPbuffer(mContextConfig, initPbufferAttribs); + if (!pbuffer) { return egl::Error(EGL_NOT_INITIALIZED, "Could not create the initialization pbuffer."); } - if (!mGLX.makeCurrent(mInitPbuffer, mContext)) + if (!mGLX.makeCurrent(pbuffer, context)) { return egl::Error(EGL_NOT_INITIALIZED, "Could not make the initialization pbuffer current."); @@ -326,38 +338,37 @@ syncXCommands(false); - mRenderer.reset(new RendererGL(std::move(functionsGL), eglAttributes, this)); - const gl::Version &maxVersion = mRenderer->getMaxSupportedESVersion(); - if (maxVersion < gl::Version(2, 0)) + outRenderer->reset( + new RendererGLX(std::move(functionsGL), eglAttributes, mGLX, this, context, pbuffer)); + + NativeContext ¤tContext = mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()]; + + if (makeNewContextCurrent) { - return egl::Error(EGL_NOT_INITIALIZED, "OpenGL ES 2.0 is not supportable."); + currentContext = {context, pbuffer}; + } + else + { + // Reset the current context back to the previous state + if (mGLX.makeCurrent(currentContext.drawable, currentContext.context) == EGL_FALSE) + { + return egl::Error(EGL_CONTEXT_LOST, "Failed to make the previous context current"); + } } - return DisplayGL::initialize(display); + return egl::NoError(); } void DisplayGLX::terminate() { DisplayGL::terminate(); - if (mInitPbuffer) - { - mGLX.destroyPbuffer(mInitPbuffer); - mInitPbuffer = 0; - } - mCurrentNativeContexts.clear(); - if (mContext) - { - mGLX.destroyContext(mContext); - mContext = nullptr; - } + mRenderer.reset(); mGLX.terminate(); - mRenderer.reset(); - if (mUsesNewXDisplay) { XCloseDisplay(mXDisplay); @@ -369,25 +380,29 @@ egl::Surface *readSurface, gl::Context *context) { - glx::Drawable newDrawable = - (drawSurface ? GetImplAs<SurfaceGLX>(drawSurface)->getDrawable() : mInitPbuffer); - glx::Context newContext = mContext; - // If the thread calling makeCurrent does not have the correct context current (either mContext - // or 0), we need to set it current. - if (!context) + NativeContext ¤tContext = mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()]; + glx::Context newContext = 0; + glx::Drawable newDrawable = 0; + if (context) { - newDrawable = 0; - newContext = 0; + ContextGLX *newContextGLX = GetImplAs<ContextGLX>(context); + newContext = newContextGLX->getContext(); + if (drawSurface) + { + newDrawable = GetImplAs<SurfaceGLX>(drawSurface)->getDrawable(); + } + else + { + newDrawable = newContextGLX->getPbuffer(); + } } - if (newDrawable != mCurrentDrawable || - newContext != mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()]) + if (newDrawable != currentContext.drawable || newContext != currentContext.context) { if (mGLX.makeCurrent(newDrawable, newContext) != True) { return egl::Error(EGL_CONTEXT_LOST, "Failed to make the GLX context current"); } - mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()] = newContext; - mCurrentDrawable = newDrawable; + currentContext = {newContext, newDrawable}; } return DisplayGL::makeCurrent(display, drawSurface, readSurface, context); @@ -464,10 +479,44 @@ { RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus = GetRobustnessVideoMemoryPurge(attribs); - return new ContextGL(state, errorSet, mRenderer, robustnessVideoMemoryPurgeStatus); + EGLAttrib virtualizationGroup = + attribs.get(EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, EGL_DONT_CARE); + bool globalTextureShareGroup = + attribs.get(EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_FALSE) == EGL_TRUE; + + std::shared_ptr<RendererGLX> renderer = mRenderer; + + if (virtualizationGroup != EGL_DONT_CARE) + { + renderer = mVirtualizationGroups[virtualizationGroup].lock(); + if (!renderer) + { + glx::Context nativeShareContext = 0; + if (globalTextureShareGroup) + { + nativeShareContext = mRenderer->getContext(); + } + else if (shareContext) + { + ContextGLX *shareContextGLX = GetImplAs<ContextGLX>(shareContext); + nativeShareContext = shareContextGLX->getContext(); + } + // Create a new renderer for this context. + egl::Error error = createRenderer(nativeShareContext, false, &renderer); + if (error.isError()) + { + ERR() << "Failed to create a shared renderer: " << error.getMessage(); + return nullptr; + } + + mVirtualizationGroups[virtualizationGroup] = renderer; + } + } + return new ContextGLX(state, errorSet, renderer, robustnessVideoMemoryPurgeStatus); } -egl::Error DisplayGLX::initializeContext(glx::FBConfig config, +egl::Error DisplayGLX::initializeContext(glx::Context shareContext, + glx::FBConfig config, const egl::AttributeMap &eglAttributes, glx::Context *context) { @@ -502,7 +551,7 @@ { profileMask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; } - return createContextAttribs(config, requestedVersion, profileMask, context); + return createContextAttribs(shareContext, config, requestedVersion, profileMask, context); } // The only way to get a core profile context of the highest version using @@ -529,7 +578,8 @@ profileFlag |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT; } - egl::Error error = createContextAttribs(config, info.version, profileFlag, context); + egl::Error error = + createContextAttribs(shareContext, config, info.version, profileFlag, context); if (!error.isError()) { return error; @@ -881,6 +931,8 @@ outExtensions->robustnessVideoMemoryPurgeNV = mHasNVRobustnessVideoMemoryPurge; + outExtensions->contextVirtualizationANGLE = true; + DisplayGL::generateExtensions(outExtensions); } @@ -903,7 +955,8 @@ return result; } -egl::Error DisplayGLX::createContextAttribs(glx::FBConfig, +egl::Error DisplayGLX::createContextAttribs(glx::Context shareContext, + glx::FBConfig, const Optional<gl::Version> &version, int profileMask, glx::Context *context) const @@ -947,7 +1000,7 @@ // (the error handler is NOT per-display). XSync(mXDisplay, False); auto oldErrorHandler = XSetErrorHandler(IgnoreX11Errors); - *context = mGLX.createContextAttribsARB(mContextConfig, nullptr, True, attribs.data()); + *context = mGLX.createContextAttribsARB(mContextConfig, shareContext, True, attribs.data()); XSetErrorHandler(oldErrorHandler); if (!*context)
diff --git a/src/libANGLE/renderer/gl/glx/DisplayGLX.h b/src/libANGLE/renderer/gl/glx/DisplayGLX.h index 6e38676..ad457a7 100644 --- a/src/libANGLE/renderer/gl/glx/DisplayGLX.h +++ b/src/libANGLE/renderer/gl/glx/DisplayGLX.h
@@ -14,9 +14,9 @@ #include "common/Optional.h" #include "libANGLE/renderer/gl/DisplayGL.h" -#include "libANGLE/renderer/gl/RendererGL.h" #include "libANGLE/renderer/gl/glx/FunctionsGLX.h" +#include "libANGLE/renderer/gl/glx/RendererGLX.h" namespace rx { @@ -95,12 +95,16 @@ void populateFeatureList(angle::FeatureList *features) override; + egl::Error createRenderer(glx::Context shareContext, + bool makeNewContextCurrent, + std::shared_ptr<RendererGLX> *outRenderer); RendererGL *getRenderer() const override; angle::NativeWindowSystem getWindowSystem() const override; private: - egl::Error initializeContext(glx::FBConfig config, + egl::Error initializeContext(glx::Context shareContext, + glx::FBConfig config, const egl::AttributeMap &eglAttributes, glx::Context *context); @@ -110,22 +114,24 @@ egl::Error makeCurrentSurfaceless(gl::Context *context) override; int getGLXFBConfigAttrib(glx::FBConfig config, int attrib) const; - egl::Error createContextAttribs(glx::FBConfig, + egl::Error createContextAttribs(glx::Context shareContext, + glx::FBConfig, const Optional<gl::Version> &version, int profileMask, glx::Context *context) const; - std::shared_ptr<RendererGL> mRenderer; + std::shared_ptr<RendererGLX> mRenderer; std::map<int, glx::FBConfig> configIdToGLXConfig; EGLint mRequestedVisual; glx::FBConfig mContextConfig; - glx::Context mContext; - angle::HashMap<uint64_t, glx::Context> mCurrentNativeContexts; - - // A pbuffer the context is current on during ANGLE initialization - glx::Pbuffer mInitPbuffer; + struct NativeContext + { + glx::Context context; + glx::Drawable drawable; + }; + angle::HashMap<uint64_t, NativeContext> mCurrentNativeContexts; bool mUsesNewXDisplay; bool mIsMesa; @@ -148,7 +154,7 @@ int mMaxSwapInterval; int mCurrentSwapInterval; - glx::Drawable mCurrentDrawable; + std::map<EGLAttrib, std::weak_ptr<RendererGLX>> mVirtualizationGroups; FunctionsGLX mGLX; Display *mXDisplay;
diff --git a/src/libANGLE/renderer/gl/glx/RendererGLX.cpp b/src/libANGLE/renderer/gl/glx/RendererGLX.cpp new file mode 100644 index 0000000..df39faf --- /dev/null +++ b/src/libANGLE/renderer/gl/glx/RendererGLX.cpp
@@ -0,0 +1,49 @@ +// +// 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. +// + +#include "libANGLE/renderer/gl/glx/RendererGLX.h" + +#include "libANGLE/renderer/gl/glx/DisplayGLX.h" + +namespace rx +{ + +RendererGLX::RendererGLX(std::unique_ptr<FunctionsGL> functionsGL, + const egl::AttributeMap &attribMap, + const FunctionsGLX &glx, + DisplayGLX *display, + glx::Context context, + glx::Pbuffer pbuffer) + : RendererGL(std::move(functionsGL), attribMap, display), + mGLX(glx), + mContext(context), + mPbuffer(pbuffer) +{} + +RendererGLX::~RendererGLX() +{ + if (mPbuffer) + { + mGLX.destroyPbuffer(mPbuffer); + } + + if (mContext) + { + mGLX.destroyContext(mContext); + } +} + +glx::Context RendererGLX::getContext() const +{ + return mContext; +} + +glx::Pbuffer RendererGLX::getPbuffer() const +{ + return mPbuffer; +} + +} // namespace rx
diff --git a/src/libANGLE/renderer/gl/glx/RendererGLX.h b/src/libANGLE/renderer/gl/glx/RendererGLX.h new file mode 100644 index 0000000..1841182 --- /dev/null +++ b/src/libANGLE/renderer/gl/glx/RendererGLX.h
@@ -0,0 +1,43 @@ +// +// 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. +// + +// RendererGLX.h: Renderer class for GL on Linux. Owns a GLX context object. + +#ifndef LIBANGLE_RENDERER_GL_GLX_RENDERERGLX_H_ +#define LIBANGLE_RENDERER_GL_GLX_RENDERERGLX_H_ + +#include "libANGLE/renderer/gl/RendererGL.h" + +#include "libANGLE/renderer/gl/glx/FunctionsGLX.h" + +#undef Success + +namespace rx +{ +class DisplayGLX; + +class RendererGLX : public RendererGL +{ + public: + RendererGLX(std::unique_ptr<FunctionsGL> functionsGL, + const egl::AttributeMap &attribMap, + const FunctionsGLX &glx, + DisplayGLX *display, + glx::Context context, + glx::Pbuffer pbuffer); + ~RendererGLX() override; + + glx::Context getContext() const; + glx::Pbuffer getPbuffer() const; + + private: + const FunctionsGLX &mGLX; + glx::Context mContext; + glx::Pbuffer mPbuffer; +}; +} // namespace rx + +#endif // LIBANGLE_RENDERER_GL_GLX_RENDERERGLX_H_
diff --git a/src/tests/angle_end2end_tests.gni b/src/tests/angle_end2end_tests.gni index bce0a90..f3a6155 100644 --- a/src/tests/angle_end2end_tests.gni +++ b/src/tests/angle_end2end_tests.gni
@@ -14,6 +14,7 @@ "egl_tests/EGLContextCompatibilityTest.cpp", "egl_tests/EGLContextPassthroughShadersTest.cpp", "egl_tests/EGLContextSharingTest.cpp", + "egl_tests/EGLContextStateTest.cpp", "egl_tests/EGLCreateContextAttribsTest.cpp", "egl_tests/EGLDebugTest.cpp", "egl_tests/EGLDisplaySelectionTest.cpp",
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index aa6be5b..90cee03 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -1592,6 +1592,26 @@ 42266575 WIN GLES : RGBTextureBufferTestES31.*/* = SKIP 42266575 LINUX NVIDIA OpenGL : RGBTextureBufferTestES31.*/* = SKIP +// Virtualized contexts on multiple contexts not supported on Win (wgl) +491346041 WIN OpenGL : EGLContextStateTest.MultipleThreads/* = SKIP +491346041 WIN GLES : EGLContextStateTest.MultipleThreads/* = SKIP + +// Failing on Desktop GL Linux when contexts are devirtualized +40096700 LINUX OPENGL : MultithreadingTestES3.SharedSrgbTextureMultipleContexts/* = SKIP +40096700 LINUX OPENGL : MultithreadingTestES3.SimultaneousUploadAndDraw/* = SKIP +40096700 LINUX OPENGL : MultithreadingTestES3.SimultaneousBufferBind/* = SKIP +40096700 LINUX OPENGL : MultithreadingTestES3.SimultaneousBufferBindAndGen/* = SKIP +40096700 LINUX OPENGL : MultithreadingTestES3.UniformBlockBinding/* = SKIP + +// Test does not yet support devirtualized contexts. +40096700 LINUX OPENGL : EGLContextSharingTest.DeleteReaderOfSharedTexture/* = SKIP + +// Failing on Intel Linux +40096700 LINUX OPENGL INTEL : MultithreadingTestES3.CreateNewContextAfterTextureUploadOnMainThread/* = SKIP +40096700 LINUX OPENGL INTEL : MultithreadingTestES3.MultithreadFenceDraw/* = SKIP +40096700 LINUX OPENGL INTEL : MultithreadingTestES3.MultithreadFenceTexImage/* = SKIP +40096700 LINUX OPENGL INTEL : MultithreadingTestES3.RenderThenSampleInNewContextWithDifferentPriority/* = SKIP + 42266091 WIN INTEL : *EmulateCopyTexImage2DFromRenderbuffers* = SKIP 42266092 WIN SWIFTSHADER : VulkanPerformanceCounterTest.EndXfbAfterRenderPassClosed/* = SKIP
diff --git a/src/tests/egl_tests/EGLBlobCacheTest.cpp b/src/tests/egl_tests/EGLBlobCacheTest.cpp index 1a77b5d..6ba4541 100644 --- a/src/tests/egl_tests/EGLBlobCacheTest.cpp +++ b/src/tests/egl_tests/EGLBlobCacheTest.cpp
@@ -153,6 +153,7 @@ { // Force disply caching off. Blob cache functions require it. forceNewDisplay(); + setPbuffer(true); } void testSetUp() override
diff --git a/src/tests/egl_tests/EGLContextSharingTest.cpp b/src/tests/egl_tests/EGLContextSharingTest.cpp index 0db1932..3f1ee22 100644 --- a/src/tests/egl_tests/EGLContextSharingTest.cpp +++ b/src/tests/egl_tests/EGLContextSharingTest.cpp
@@ -60,6 +60,29 @@ 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; + } + EGLContext mContexts[2] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT}; GLuint mTexture; }; @@ -105,30 +128,6 @@ 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) @@ -944,7 +943,7 @@ EGLContext ctx; EGLSurface srf; EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); EXPECT_TRUE(createContext(config, &ctx)); EXPECT_TRUE(createPbufferSurface(mDisplay, config, 1280, 720, &srf)); @@ -972,7 +971,7 @@ EGLContext ctx; EGLSurface srf; EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); EXPECT_TRUE(createContext(config, &ctx)); EXPECT_TRUE(createPbufferSurface(mDisplay, config, 1280, 720, &srf)); @@ -1019,7 +1018,7 @@ EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); @@ -1072,7 +1071,7 @@ EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + 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 @@ -1137,7 +1136,7 @@ EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); @@ -1190,7 +1189,7 @@ EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + 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 @@ -1273,7 +1272,7 @@ EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); @@ -1316,7 +1315,7 @@ EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight); EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface)); @@ -1586,7 +1585,7 @@ EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr)); EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + 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, @@ -1618,19 +1617,6 @@ ASSERT_EGL_SUCCESS() << "Error during EGLPriorityContextSharingTestNoFixture 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_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; - } - EGLDisplay mDisplay = EGL_NO_DISPLAY; const EGLint kWidth = 64; const EGLint kHeight = 64; @@ -1643,7 +1629,7 @@ ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_IMG_context_priority")); EGLConfig config = EGL_NO_CONFIG_KHR; - EXPECT_TRUE(chooseConfig(&config)); + EXPECT_TRUE(chooseConfig(mDisplay, &config)); // Initialize contexts constexpr size_t kContextCount = 2;
diff --git a/src/tests/egl_tests/EGLContextStateTest.cpp b/src/tests/egl_tests/EGLContextStateTest.cpp new file mode 100644 index 0000000..0b0c3c7 --- /dev/null +++ b/src/tests/egl_tests/EGLContextStateTest.cpp
@@ -0,0 +1,110 @@ +// +// 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. +// +// EGLContextStateTest.cpp: +// Tests relating to Context state. + +#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 +{ + +class EGLContextStateTest : public ANGLETest<> +{ + public: + void testSetUp() override + { + EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), 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)); + + int nConfigs = 0; + ASSERT_EGL_TRUE(eglGetConfigs(mDisplay, nullptr, 0, &nConfigs)); + ASSERT_GE(nConfigs, 1); + + int nReturnedConfigs = 0; + std::vector<EGLConfig> configs(nConfigs); + ASSERT_EGL_TRUE(eglGetConfigs(mDisplay, configs.data(), nConfigs, &nReturnedConfigs)); + ASSERT_EQ(nConfigs, nReturnedConfigs); + mConfig = configs[0]; + } + + EGLContext createContext(EGLConfig config) + { + EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, + mVirtualizationGroup++, EGL_NONE}; + if (!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_context_virtualization")) + { + attribs[2] = EGL_NONE; + } + + EGLContext context = eglCreateContext(mDisplay, config, nullptr, attribs); + EXPECT_NE(context, EGL_NO_CONTEXT); + return context; + } + + void testTearDown() override {} + + EGLDisplay mDisplay; + EGLConfig mConfig; + std::atomic<EGLint> mVirtualizationGroup; +}; + +// Test eglMakeCurrent() and eglGetCurrentContext() on multiple threads. +TEST_P(EGLContextStateTest, MultipleThreads) +{ + EGLContext contexts[2] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT}; + + contexts[0] = createContext(mConfig); + contexts[1] = createContext(mConfig); + + { + EXPECT_EQ(eglGetCurrentContext(), EGL_NO_CONTEXT); + EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, contexts[0])); + EXPECT_EQ(eglGetCurrentContext(), contexts[0]); + EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, contexts[1])); + EXPECT_EQ(eglGetCurrentContext(), contexts[1]); + } + + std::thread thread([this, contexts]() { + auto prevContext = eglGetCurrentContext(); + EXPECT_EQ(prevContext, EGL_NO_CONTEXT); + EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, contexts[0])); + EXPECT_EQ(eglGetCurrentContext(), contexts[0]); + EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, prevContext)); + EXPECT_EQ(eglGetCurrentContext(), prevContext); + }); + thread.join(); + EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); +} + +ANGLE_INSTANTIATE_TEST(EGLContextStateTest, + WithNoFixture(ES2_D3D9()), + WithNoFixture(ES2_D3D11()), + WithNoFixture(ES3_D3D11()), + WithNoFixture(ES2_METAL()), + WithNoFixture(ES3_METAL()), + WithNoFixture(ES2_OPENGL()), + WithNoFixture(ES3_OPENGL()), + WithNoFixture(ES2_OPENGLES()), + WithNoFixture(ES3_OPENGLES()), + WithNoFixture(ES2_VULKAN()), + WithNoFixture(ES3_VULKAN())); + +} // namespace
diff --git a/src/tests/egl_tests/EGLMultiContextTest.cpp b/src/tests/egl_tests/EGLMultiContextTest.cpp index de69b58..053d5f7 100644 --- a/src/tests/egl_tests/EGLMultiContextTest.cpp +++ b/src/tests/egl_tests/EGLMultiContextTest.cpp
@@ -38,7 +38,10 @@ class EGLMultiContextTest : public ANGLETest<> { public: - EGLMultiContextTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) {} + EGLMultiContextTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) + { + setPbuffer(true); + } void testTearDown() override { @@ -119,6 +122,12 @@ Flush, Finish, }; + + bool hasFenceSyncExtension() const + { + return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_fence_sync"); + } + void testFenceWithOpenRenderPass(FenceTest test, FlushMethod flushMethod); EGLContext mContexts[2]; @@ -459,6 +468,7 @@ void EGLMultiContextTest::testFenceWithOpenRenderPass(FenceTest test, FlushMethod flushMethod) { ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); + ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension()); constexpr uint32_t kWidth = 100; constexpr uint32_t kHeight = 200;
diff --git a/src/tests/gl_tests/ImageTest.cpp b/src/tests/gl_tests/ImageTest.cpp index 411bb51..357d150 100644 --- a/src/tests/gl_tests/ImageTest.cpp +++ b/src/tests/gl_tests/ImageTest.cpp
@@ -157,6 +157,7 @@ setConfigBlueBits(8); setConfigAlphaBits(8); setConfigDepthBits(24); + setPbuffer(true); } const char *getVS() const
diff --git a/src/tests/gl_tests/MultithreadingTest.cpp b/src/tests/gl_tests/MultithreadingTest.cpp index 7d28662..11fd062 100644 --- a/src/tests/gl_tests/MultithreadingTest.cpp +++ b/src/tests/gl_tests/MultithreadingTest.cpp
@@ -37,6 +37,7 @@ setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); + setPbuffer(true); } bool hasFenceSyncExtension() const
diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp index 8394357..63476f2 100644 --- a/src/tests/test_utils/ANGLETest.cpp +++ b/src/tests/test_utils/ANGLETest.cpp
@@ -1676,6 +1676,11 @@ mFixture->configParams.robustResourceInit = enabled; } +void ANGLETestBase::setPbuffer(bool enabled) +{ + mFixture->configParams.pbuffer = enabled; +} + void ANGLETestBase::setMutableRenderBuffer(bool enabled) { mFixture->configParams.mutableRenderBuffer = enabled;
diff --git a/src/tests/test_utils/ANGLETest.h b/src/tests/test_utils/ANGLETest.h index 1910cc1..fd49f8f 100644 --- a/src/tests/test_utils/ANGLETest.h +++ b/src/tests/test_utils/ANGLETest.h
@@ -527,6 +527,7 @@ void setBindGeneratesResource(bool bindGeneratesResource); void setClientArraysEnabled(bool enabled); void setRobustResourceInit(bool enabled); + void setPbuffer(bool enabled); void setMutableRenderBuffer(bool enabled); void setContextProgramCacheEnabled(bool enabled); void setContextResetStrategy(EGLenum resetStrategy);
diff --git a/util/EGLWindow.cpp b/util/EGLWindow.cpp index 3a2fb54..fd71983 100644 --- a/util/EGLWindow.cpp +++ b/util/EGLWindow.cpp
@@ -53,6 +53,8 @@ clientArraysEnabled(true), // The default value of EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT is EGL_FALSE. robustAccess(false), + // Tests that require EGL_PBUFFER_BIT should request it explicitly. + pbuffer(false), // EGL_RENDER_BUFFER requires EGL 1.4+ or extension support. mutableRenderBuffer(false), samples(-1), @@ -339,7 +341,8 @@ std::vector<EGLint> configAttributes = { EGL_SURFACE_TYPE, - EGL_WINDOW_BIT | (params.mutableRenderBuffer ? EGL_MUTABLE_RENDER_BUFFER_BIT_KHR : 0), + EGL_WINDOW_BIT | (params.pbuffer ? EGL_PBUFFER_BIT : 0) | + (params.mutableRenderBuffer ? EGL_MUTABLE_RENDER_BUFFER_BIT_KHR : 0), EGL_RED_SIZE, (mConfigParams.redBits >= 0) ? mConfigParams.redBits : EGL_DONT_CARE, EGL_GREEN_SIZE,
diff --git a/util/EGLWindow.h b/util/EGLWindow.h index 99256fe..6cd687f 100644 --- a/util/EGLWindow.h +++ b/util/EGLWindow.h
@@ -55,6 +55,7 @@ bool bindGeneratesResource; bool clientArraysEnabled; bool robustAccess; + bool pbuffer; bool mutableRenderBuffer; EGLint samples; bool contextProgramCacheEnabled;