Perform Display terminate(InternalCleanup) from makeCurrent()

Current state:
- Call to `eglTerminate()` is canceled if there are Contexts current.
- All not current Contexts are invalidated in `eglTerminate()` call.
- Full Display termination will happen only when last Context is
  actually destroyed in `eglDestroyContext()` or last active thread
  terminates (Android only).

Problem:
- Context is not marked invalid after it is unmade from current when
  `eglTerminate()` was already called.
- If `eglDestroyContext()` was called while context was current, it
  will be destroyed when unmade from current - in this case actual
  Display termination will NOT happen.

After this change:
- Context immediately invalidated after it is unmade from current when
  `eglTerminate()` was already called.
- Full Display termination will happen after the last Context is unmade
  from current (all Contexts are invalid).

Bug: angleproject:6798
Test: angle_end2end_tests --gtest_filter=EGLDisplayTest.ContextLeakAfterTerminate*
Change-Id: Idcce94b041649db58d3d879858fba99109347baf
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4708328
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Igor Nazarov <i.nazarov@samsung.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
This commit is contained in:
Igor Nazarov
2023-07-24 13:00:32 +03:00
committed by Angle LUCI CQ
parent 6fb3295db8
commit c95ffadf76
2 changed files with 82 additions and 17 deletions

View File

@@ -1314,6 +1314,11 @@ Error Display::prepareForCall()
Error Display::releaseThread()
{
// Need to check if initialized, because makeCurrent() may terminate the Display.
if (!mInitialized)
{
return NoError();
}
ANGLE_TRY(mImplementation->releaseThread());
return destroyInvalidEglObjects();
}
@@ -1742,6 +1747,14 @@ Error Display::makeCurrent(Thread *thread,
}
}
// If eglTerminate() has previously been called and Context was changed, perform InternalCleanup
// to invalidate any non-current Contexts, and possibly fully terminate the Display and release
// all of its resources.
if (mTerminatedByApi && contextChanged)
{
return terminate(thread, TerminateReason::InternalCleanup);
}
return NoError();
}
@@ -1904,21 +1917,6 @@ Error Display::destroyContext(Thread *thread, gl::Context *context)
makeCurrent(thread, context, currentDrawSurface, currentReadSurface, currentContext));
}
// If eglTerminate() has previously been called and this is the last Context the Display owns,
// we can now fully terminate the display and release all of its resources.
if (mTerminatedByApi)
{
for (auto ctx : mState.contextMap)
{
if (ctx.second->isReferenced())
{
return NoError();
}
}
return terminate(thread, TerminateReason::InternalCleanup);
}
return NoError();
}

View File

@@ -12,10 +12,49 @@
using namespace angle;
class EGLDisplayTest : public ANGLETest<>
{};
{
protected:
EGLConfig chooseConfig(EGLDisplay display)
{
const EGLint attribs[] = {EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE,
EGL_PBUFFER_BIT,
EGL_NONE};
EGLConfig config = EGL_NO_CONFIG_KHR;
EGLint count = 0;
EXPECT_EGL_TRUE(eglChooseConfig(display, attribs, &config, 1, &count));
EXPECT_EGL_TRUE(count > 0);
return config;
}
EGLContext createContext(EGLDisplay display, EGLConfig config)
{
const EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE};
EGLContext context = eglCreateContext(display, config, nullptr, attribs);
EXPECT_NE(context, EGL_NO_CONTEXT);
return context;
}
EGLSurface createSurface(EGLDisplay display, EGLConfig config)
{
const EGLint attribs[] = {EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE};
EGLSurface surface = eglCreatePbufferSurface(display, config, attribs);
EXPECT_NE(surface, EGL_NO_SURFACE);
return surface;
}
};
// Tests that an EGLDisplay can be re-initialized.
TEST_P(EGLDisplayTest, InitalizeTerminateInitalize)
TEST_P(EGLDisplayTest, InitializeTerminateInitialize)
{
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EXPECT_EGL_TRUE(eglInitialize(display, nullptr, nullptr) != EGL_FALSE);
@@ -23,6 +62,34 @@ TEST_P(EGLDisplayTest, InitalizeTerminateInitalize)
EXPECT_EGL_TRUE(eglInitialize(display, nullptr, nullptr) != EGL_FALSE);
}
// Tests current Context leaking when call eglTerminate() while it is current.
TEST_P(EGLDisplayTest, ContextLeakAfterTerminate)
{
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EXPECT_EGL_TRUE(eglInitialize(display, nullptr, nullptr));
EGLConfig config = chooseConfig(display);
EGLContext context = createContext(display, config);
EGLSurface surface = createSurface(display, config);
// Make "context" current.
EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context));
// Terminate display while "context" is current.
EXPECT_EGL_TRUE(eglTerminate(display));
// Unmake "context" from current and allow Display to actually terminate.
(void)eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
// Get EGLDisplay again.
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
// Check if Display was actually terminated.
EGLint val;
EXPECT_EGL_FALSE(eglQueryContext(display, context, EGL_CONTEXT_CLIENT_TYPE, &val));
EXPECT_EQ(eglGetError(), EGL_NOT_INITIALIZED);
}
ANGLE_INSTANTIATE_TEST(EGLDisplayTest,
WithNoFixture(ES2_D3D9()),
WithNoFixture(ES2_D3D11()),