diff --git a/tests/Makefile.am b/tests/Makefile.am index 54c46e6..2c72bf6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -179,6 +179,7 @@ TESTS_EGL += testeglgetprocaddress.sh TESTS_EGL += testeglmakecurrent.sh TESTS_EGL += testeglerror.sh TESTS_EGL += testegldebug.sh +TESTS_EGL += testeglcurrentcleanup.sh if ENABLE_EGL @@ -229,6 +230,12 @@ testegldebug_SOURCES = \ egl_test_utils.c testegldebug_LDADD = $(top_builddir)/src/EGL/libEGL.la @LIB_DL@ +check_PROGRAMS += testeglcurrentcleanup +testeglcurrentcleanup_SOURCES = \ + testeglcurrentcleanup.c +testeglcurrentcleanup_LDADD = $(top_builddir)/src/EGL/libEGL.la +testeglcurrentcleanup_LDADD += $(PTHREAD_LIBS) + endif # ENABLE_EGL EXTRA_DIST += $(TESTS_GLX) $(TESTS_EGL) diff --git a/tests/dummy/EGL_dummy.c b/tests/dummy/EGL_dummy.c index e21dfee..6e141f1 100644 --- a/tests/dummy/EGL_dummy.c +++ b/tests/dummy/EGL_dummy.c @@ -85,6 +85,9 @@ static struct glvnd_list displayList = { &displayList, &displayList }; static glvnd_mutex_t displayListLock = GLVND_MUTEX_INITIALIZER; static EGLint failNextMakeCurrentError = EGL_NONE; +static glvnd_mutex_t contextListLock = GLVND_MUTEX_INITIALIZER; +static struct glvnd_list contextList = { &contextList, &contextList }; + static EGLDEBUGPROCKHR debugCallbackFunc = NULL; static EGLBoolean debugCallbackEnabled = EGL_TRUE; @@ -112,9 +115,20 @@ static DummyThreadState *GetThreadState(void) return thr; } +void DestroyThreadState(DummyThreadState *thr) +{ + if (thr != NULL) + { + __glvndPthreadFuncs.mutex_lock(&threadStateLock); + glvnd_list_del(&thr->entry); + __glvndPthreadFuncs.mutex_unlock(&threadStateLock); + free(thr); + } +} + static void OnThreadTerminate(void *ptr) { - free(ptr); + DestroyThreadState((DummyThreadState *) ptr); } static void CommonEntrypoint(void) @@ -326,6 +340,10 @@ static EGLContext EGLAPIENTRY dummy_eglCreateContext(EGLDisplay dpy, dctx = (DummyEGLContext *) calloc(1, sizeof(DummyEGLContext)); dctx->vendorName = DUMMY_VENDOR_NAME; + __glvndPthreadFuncs.mutex_lock(&contextListLock); + glvnd_list_append(&dctx->entry, &contextList); + __glvndPthreadFuncs.mutex_unlock(&contextListLock); + return (EGLContext) dctx; } @@ -336,6 +354,9 @@ static EGLBoolean EGLAPIENTRY dummy_eglDestroyContext(EGLDisplay dpy, EGLContext if (ctx != EGL_NO_CONTEXT) { DummyEGLContext *dctx = (DummyEGLContext *) ctx; + __glvndPthreadFuncs.mutex_lock(&contextListLock); + glvnd_list_del(&dctx->entry); + __glvndPthreadFuncs.mutex_unlock(&contextListLock); free(dctx); } return EGL_TRUE; @@ -504,7 +525,7 @@ static EGLBoolean EGLAPIENTRY dummy_eglReleaseThread(void) __glvndPthreadFuncs.getspecific(threadStateKey); if (thr != NULL) { __glvndPthreadFuncs.setspecific(threadStateKey, NULL); - free(thr); + DestroyThreadState(thr); } return EGL_TRUE; } @@ -957,6 +978,14 @@ void _fini(void) return; } + __glvndPthreadFuncs.mutex_lock(&contextListLock); + while (!glvnd_list_is_empty(&contextList)) { + DummyEGLContext *ctx = glvnd_list_first_entry(&contextList, DummyEGLContext, entry); + glvnd_list_del(&ctx->entry); + free(ctx); + } + __glvndPthreadFuncs.mutex_unlock(&contextListLock); + while (!glvnd_list_is_empty(&displayList)) { DummyEGLDisplay *disp = glvnd_list_first_entry( &displayList, DummyEGLDisplay, entry); diff --git a/tests/dummy/EGL_dummy.h b/tests/dummy/EGL_dummy.h index 01b4a1d..93f2158 100644 --- a/tests/dummy/EGL_dummy.h +++ b/tests/dummy/EGL_dummy.h @@ -52,6 +52,8 @@ #include #include +#include "glvnd_list.h" + #define DUMMY_VENDOR_NAME_0 "dummy0" #define DUMMY_VENDOR_NAME_1 "dummy1" @@ -106,6 +108,9 @@ enum */ typedef struct DummyEGLContextRec { const char *vendorName; + + // Everything after this is used internally by EGL_dummy.c. + struct glvnd_list entry; } DummyEGLContext; /** diff --git a/tests/meson.build b/tests/meson.build index 886ffea..d6d8abc 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -232,5 +232,19 @@ if get_option('egl') suite : ['egl'], ) endforeach + + test( + 'eglcurrentcleanup', + executable( + 'eglcurrentcleanup', + ['testeglcurrentcleanup.c'], + include_directories : [inc_include], + link_with : [libEGL], + dependencies : [dep_threads], + ), + args : [ '-m', '-t', '-k', '-r' ], + env : env_egl, + suite : ['egl'], + ) endif diff --git a/tests/testeglcurrentcleanup.c b/tests/testeglcurrentcleanup.c new file mode 100644 index 0000000..a243656 --- /dev/null +++ b/tests/testeglcurrentcleanup.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2021, NVIDIA CORPORATION. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * unaltered in all copies or substantial portions of the Materials. + * Any additions, deletions, or changes to the original source files + * must be clearly indicated in accompanying documentation. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dummy/EGL_dummy.h" + +static EGLDisplay dpy = EGL_NO_DISPLAY; +static sem_t worker_ready_semaphore; + +void init_context(void) +{ + EGLContext ctx = eglCreateContext(dpy, NULL, EGL_NO_CONTEXT, NULL); + if (ctx == EGL_NO_CONTEXT) { + printf("eglCreateContext failed\n"); + fflush(stdout); + abort(); + } + + if (!eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx)) { + printf("eglMakeCurrent failed\n"); + fflush(stdout); + abort(); + } +} + +void *worker_proc(void *param) +{ + init_context(); + + return NULL; +} + +void *worker_release_thread_proc(void *param) +{ + init_context(); + + eglReleaseThread(); + + return NULL; +} + + +void *worker_keep_proc(void *param) +{ + init_context(); + + sem_post(&worker_ready_semaphore); + + while (1) + { + sleep(1); + } + + return NULL; +} + +int main(int argc, char **argv) +{ + const struct option OPTIONS[] = { + { "main", no_argument, NULL, 'm' }, + { "thread", no_argument, NULL, 't' }, + { "release-thread", no_argument, NULL, 'r' }, + { "thread-keep", no_argument, NULL, 'k' }, + { NULL } + }; + + int option_main = 0; + int option_thread = 0; + int option_release_thread = 0; + int option_thread_keep = 0; + + EGLint major, minor; + + while (1) + { + int c = getopt_long(argc, argv, "hmtrk", OPTIONS, NULL); + if (c == -1) + { + break; + } + switch (c) + { + case 'm': + option_main = 1; + break; + case 't': + option_thread = 1; + break; + case 'r': + option_release_thread = 1; + break; + case 'k': + option_thread_keep = 1; + break; + default: + return 2; + } + } + + sem_init(&worker_ready_semaphore, 0, 0); + + dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (dpy == EGL_NO_DISPLAY) { + printf("eglGetDisplay failed\n"); + return 2; + } + if (!eglInitialize(dpy, &major, &minor)) { + printf("eglInitialize failed\n"); + return 2; + } + + if (option_main) { + printf("Setting current context on main thread\n"); + init_context(); + } + + if (option_thread) { + pthread_t thread; + + printf("Starting and terminating worker thread\n"); + if (pthread_create(&thread, NULL, worker_proc, dpy) != 0) { + printf("pthread_create failed\n"); + return 2; + } + + pthread_join(thread, NULL); + } + + if (option_release_thread) { + pthread_t thread; + + printf("Starting and terminating worker thread\n"); + if (pthread_create(&thread, NULL, worker_release_thread_proc, dpy) != 0) { + printf("pthread_create failed\n"); + return 2; + } + + pthread_join(thread, NULL); + } + + + if (option_thread_keep) { + pthread_t thread; + + printf("Starting and keeping worker thread\n"); + if (pthread_create(&thread, NULL, worker_keep_proc, dpy) != 0) { + printf("pthread_create failed\n"); + return 2; + } + + sem_wait(&worker_ready_semaphore); + } + + return 0; +} diff --git a/tests/testeglcurrentcleanup.sh b/tests/testeglcurrentcleanup.sh new file mode 100755 index 0000000..814a857 --- /dev/null +++ b/tests/testeglcurrentcleanup.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +. $TOP_SRCDIR/tests/eglenv.sh + +./testeglcurrentcleanup -m -t -k -r