1/**************************************************************************
2 *
3 * Copyright 2009-2010 Chia-I Wu <olvaffe@gmail.com>
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sub license, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial portions
16 * of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 **************************************************************************/
27
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <stdarg.h>
33#include "c99_compat.h"
34#include "c11/threads.h"
35
36#include "egllog.h"
37#include "eglcurrent.h"
38#include "eglglobals.h"
39
40/* This should be kept in sync with _eglInitThreadInfo() */
41#define _EGL_THREAD_INFO_INITIALIZER \
42   { EGL_SUCCESS, NULL, EGL_OPENGL_ES_API, NULL, NULL, NULL }
43
44/* a fallback thread info to guarantee that every thread always has one */
45static _EGLThreadInfo dummy_thread = _EGL_THREAD_INFO_INITIALIZER;
46static mtx_t _egl_TSDMutex = _MTX_INITIALIZER_NP;
47static EGLBoolean _egl_TSDInitialized;
48static tss_t _egl_TSD;
49static void (*_egl_FreeTSD)(_EGLThreadInfo *);
50
51#ifdef GLX_USE_TLS
52static __thread const _EGLThreadInfo *_egl_TLS
53   __attribute__ ((tls_model("initial-exec")));
54#endif
55
56static inline void _eglSetTSD(const _EGLThreadInfo *t)
57{
58   tss_set(_egl_TSD, (void *) t);
59#ifdef GLX_USE_TLS
60   _egl_TLS = t;
61#endif
62}
63
64static inline _EGLThreadInfo *_eglGetTSD(void)
65{
66#ifdef GLX_USE_TLS
67   return (_EGLThreadInfo *) _egl_TLS;
68#else
69   return (_EGLThreadInfo *) tss_get(_egl_TSD);
70#endif
71}
72
73static inline void _eglFiniTSD(void)
74{
75   mtx_lock(&_egl_TSDMutex);
76   if (_egl_TSDInitialized) {
77      _EGLThreadInfo *t = _eglGetTSD();
78
79      _egl_TSDInitialized = EGL_FALSE;
80      if (t && _egl_FreeTSD)
81         _egl_FreeTSD((void *) t);
82      tss_delete(_egl_TSD);
83   }
84   mtx_unlock(&_egl_TSDMutex);
85}
86
87static inline EGLBoolean _eglInitTSD(void (*dtor)(_EGLThreadInfo *))
88{
89   if (!_egl_TSDInitialized) {
90      mtx_lock(&_egl_TSDMutex);
91
92      /* check again after acquiring lock */
93      if (!_egl_TSDInitialized) {
94         if (tss_create(&_egl_TSD, (void (*)(void *)) dtor) != thrd_success) {
95            mtx_unlock(&_egl_TSDMutex);
96            return EGL_FALSE;
97         }
98         _egl_FreeTSD = dtor;
99         _eglAddAtExitCall(_eglFiniTSD);
100         _egl_TSDInitialized = EGL_TRUE;
101      }
102
103      mtx_unlock(&_egl_TSDMutex);
104   }
105
106   return EGL_TRUE;
107}
108
109static void
110_eglInitThreadInfo(_EGLThreadInfo *t)
111{
112   memset(t, 0, sizeof(*t));
113   t->LastError = EGL_SUCCESS;
114   /* default, per EGL spec */
115   t->CurrentAPI = EGL_OPENGL_ES_API;
116}
117
118
119/**
120 * Allocate and init a new _EGLThreadInfo object.
121 */
122static _EGLThreadInfo *
123_eglCreateThreadInfo(void)
124{
125   _EGLThreadInfo *t = calloc(1, sizeof(_EGLThreadInfo));
126   if (t)
127      _eglInitThreadInfo(t);
128   else
129      t = &dummy_thread;
130   return t;
131}
132
133
134/**
135 * Delete/free a _EGLThreadInfo object.
136 */
137static void
138_eglDestroyThreadInfo(_EGLThreadInfo *t)
139{
140   if (t != &dummy_thread)
141      free(t);
142}
143
144
145/**
146 * Make sure TSD is initialized and return current value.
147 */
148static inline _EGLThreadInfo *
149_eglCheckedGetTSD(void)
150{
151   if (_eglInitTSD(&_eglDestroyThreadInfo) != EGL_TRUE) {
152      _eglLog(_EGL_FATAL, "failed to initialize \"current\" system");
153      return NULL;
154   }
155
156   return _eglGetTSD();
157}
158
159
160/**
161 * Return the calling thread's thread info.
162 * If the calling thread nevers calls this function before, or if its thread
163 * info was destroyed, a new one is created.  This function never returns NULL.
164 * In the case allocation fails, a dummy one is returned.  See also
165 * _eglIsCurrentThreadDummy.
166 */
167_EGLThreadInfo *
168_eglGetCurrentThread(void)
169{
170   _EGLThreadInfo *t = _eglCheckedGetTSD();
171   if (!t) {
172      t = _eglCreateThreadInfo();
173      _eglSetTSD(t);
174   }
175
176   return t;
177}
178
179
180/**
181 * Destroy the calling thread's thread info.
182 */
183void
184_eglDestroyCurrentThread(void)
185{
186   _EGLThreadInfo *t = _eglCheckedGetTSD();
187   if (t) {
188      _eglDestroyThreadInfo(t);
189      _eglSetTSD(NULL);
190   }
191}
192
193
194/**
195 * Return true if the calling thread's thread info is dummy.
196 * A dummy thread info is shared by all threads and should not be modified.
197 * Functions like eglBindAPI or eglMakeCurrent should check for dummy-ness
198 * before updating the thread info.
199 */
200EGLBoolean
201_eglIsCurrentThreadDummy(void)
202{
203   _EGLThreadInfo *t = _eglCheckedGetTSD();
204   return (!t || t == &dummy_thread);
205}
206
207
208/**
209 * Return the currently bound context of the current API, or NULL.
210 */
211_EGLContext *
212_eglGetCurrentContext(void)
213{
214   _EGLThreadInfo *t = _eglGetCurrentThread();
215   return t->CurrentContext;
216}
217
218
219/**
220 * Record EGL error code and return EGL_FALSE.
221 */
222static EGLBoolean
223_eglInternalError(EGLint errCode, const char *msg)
224{
225   _EGLThreadInfo *t = _eglGetCurrentThread();
226
227   if (t == &dummy_thread)
228      return EGL_FALSE;
229
230   t->LastError = errCode;
231
232   if (errCode != EGL_SUCCESS) {
233      const char *s;
234
235      switch (errCode) {
236      case EGL_BAD_ACCESS:
237         s = "EGL_BAD_ACCESS";
238         break;
239      case EGL_BAD_ALLOC:
240         s = "EGL_BAD_ALLOC";
241         break;
242      case EGL_BAD_ATTRIBUTE:
243         s = "EGL_BAD_ATTRIBUTE";
244         break;
245      case EGL_BAD_CONFIG:
246         s = "EGL_BAD_CONFIG";
247         break;
248      case EGL_BAD_CONTEXT:
249         s = "EGL_BAD_CONTEXT";
250         break;
251      case EGL_BAD_CURRENT_SURFACE:
252         s = "EGL_BAD_CURRENT_SURFACE";
253         break;
254      case EGL_BAD_DISPLAY:
255         s = "EGL_BAD_DISPLAY";
256         break;
257      case EGL_BAD_MATCH:
258         s = "EGL_BAD_MATCH";
259         break;
260      case EGL_BAD_NATIVE_PIXMAP:
261         s = "EGL_BAD_NATIVE_PIXMAP";
262         break;
263      case EGL_BAD_NATIVE_WINDOW:
264         s = "EGL_BAD_NATIVE_WINDOW";
265         break;
266      case EGL_BAD_PARAMETER:
267         s = "EGL_BAD_PARAMETER";
268         break;
269      case EGL_BAD_SURFACE:
270         s = "EGL_BAD_SURFACE";
271         break;
272      case EGL_NOT_INITIALIZED:
273         s = "EGL_NOT_INITIALIZED";
274         break;
275      default:
276         s = "other EGL error";
277      }
278      _eglLog(_EGL_DEBUG, "EGL user error 0x%x (%s) in %s\n", errCode, s, msg);
279   }
280
281   return EGL_FALSE;
282}
283
284EGLBoolean
285_eglError(EGLint errCode, const char *msg)
286{
287   if (errCode != EGL_SUCCESS) {
288      EGLint type;
289      if (errCode == EGL_BAD_ALLOC) {
290         type = EGL_DEBUG_MSG_CRITICAL_KHR;
291      } else {
292         type = EGL_DEBUG_MSG_ERROR_KHR;
293      }
294
295      _eglDebugReport(errCode, msg, type, NULL);
296   } else
297      _eglInternalError(errCode, msg);
298
299   return EGL_FALSE;
300}
301
302/**
303 * Returns the label set for the current thread.
304 */
305EGLLabelKHR
306_eglGetThreadLabel(void)
307{
308   _EGLThreadInfo *t = _eglGetCurrentThread();
309   return t->Label;
310}
311
312static void
313_eglDebugReportFullv(EGLenum error, const char *command, const char *funcName,
314      EGLint type, EGLLabelKHR objectLabel, const char *message, va_list args)
315{
316   EGLDEBUGPROCKHR callback = NULL;
317
318   mtx_lock(_eglGlobal.Mutex);
319   if (_eglGlobal.debugTypesEnabled & DebugBitFromType(type)) {
320      callback = _eglGlobal.debugCallback;
321   }
322   mtx_unlock(_eglGlobal.Mutex);
323
324   if (callback != NULL) {
325      char *buf = NULL;
326
327      if (message != NULL) {
328         if (vasprintf(&buf, message, args) < 0) {
329            buf = NULL;
330         }
331      }
332      callback(error, command, type, _eglGetThreadLabel(), objectLabel, buf);
333      free(buf);
334   }
335
336   if (type == EGL_DEBUG_MSG_CRITICAL_KHR || type == EGL_DEBUG_MSG_ERROR_KHR) {
337      _eglInternalError(error, funcName);
338   }
339}
340
341void
342_eglDebugReportFull(EGLenum error, const char *command, const char *funcName,
343      EGLint type, EGLLabelKHR objectLabel, const char *message, ...)
344{
345   va_list args;
346   va_start(args, message);
347   _eglDebugReportFullv(error, command, funcName, type, objectLabel, message, args);
348   va_end(args);
349}
350
351void
352_eglDebugReport(EGLenum error, const char *funcName,
353      EGLint type, const char *message, ...)
354{
355   _EGLThreadInfo *thr = _eglGetCurrentThread();
356   va_list args;
357
358   if (funcName == NULL) {
359      funcName = thr->CurrentFuncName;
360   }
361
362   va_start(args, message);
363   _eglDebugReportFullv(error, thr->CurrentFuncName, funcName, type, thr->CurrentObjectLabel, message, args);
364   va_end(args);
365}
366