1 /* QuesoGLC
2  * A free implementation of the OpenGL Character Renderer (GLC)
3  * Copyright (c) 2002, 2004-2009, Bertrand Coconnier
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2.1 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 /* $Id: global.c 873 2009-01-18 17:51:17Z bcoconni $ */
20 
21 /** \file
22  *  defines the so-called "Global commands" described in chapter 3.4 of the
23  *  GLC specs.
24  */
25 
26 /** \defgroup global Global Commands
27  *  Commands to create, manage and destroy GLC contexts.
28  *
29  *  Those commands do not use GLC context state variables and can therefore be
30  *  executed successfully if the issuing thread has no current GLC context.
31  *
32  *  Each GLC context has a nonzero ID of type \b GLint. When a client is linked
33  *  with a GLC library, the library maintains a list of IDs that contains one
34  *  entry for each of the client's GLC contexts. The list is initially empty.
35  *
36  *  Each client thread has a private GLC context ID variable that always
37  *  contains either the value zero, indicating that the thread has no current
38  *  GLC context, or the ID of the thread's current GLC context. The initial
39  *  value is zero.
40  *
41  *  When the ID of a GLC context is stored in the GLC context ID variable of a
42  *  client thread, the context is said to be current to the thread. It is not
43  *  possible for a GLC context to be current simultaneously to multiple
44  *  threads. With the exception of the per-thread GLC error code and context ID
45  *  variables, all of the GLC state variables that are used during the
46  *  execution of a GLC command are stored in the issuing thread's current GLC
47  *  context. To make a context current, call glcContext().
48  *
49  *  When a client thread issues a GLC command, the thread's current GLC context
50  *  executes the command.
51  *
52  *  Note that the results of issuing a GL command when there is no current GL
53  *  context are undefined. Because GLC issues GL commands, you must create a GL
54  *  context and make it current before calling GLC.
55  *
56  *  All other GLC commands raise \b GLC_STATE_ERROR if the issuing thread has
57  *  no current GLC context.
58  */
59 
60 #include "internal.h"
61 #include <stdlib.h>
62 
63 #ifdef __GNUC__
64 __attribute__((constructor)) void init(void);
65 __attribute__((destructor)) void fini(void);
66 #else
67 void _init(void);
68 void _fini(void);
69 #endif
70 
71 
72 
73 
74 /* Since the common area can be accessed by any thread, this function should
75  * be called before any access (read or write) to the common area. Otherwise
76  * race conditions can occur. This function must also be used whenever we call
77  * a function which is not reentrant (it is the case for some Fontconfig
78  * entries).
79  * __glcLock/__glcUnlock can be nested : they keep track of the number of
80  * time they have been called and the mutex will be released as soon as
81  * __glcUnlock() will be called as many time as __glcLock() was.
82  */
__glcLock(void)83 void __glcLock(void)
84 {
85   __GLCthreadArea *area = NULL;
86 
87   area = GLC_GET_THREAD_AREA();
88   assert(area);
89 
90   if (!area->lockState)
91 #ifdef __WIN32__
92     EnterCriticalSection(&__glcCommonArea.section);
93 #else
94     pthread_mutex_lock(&__glcCommonArea.mutex);
95 #endif
96 
97   area->lockState++;
98 }
99 
100 
101 
102 /* Unlock the mutex in order to allow other threads to make accesses to the
103  * common area.
104  * See also the note on nested calls in __glcLock's description.
105  */
__glcUnlock(void)106 void __glcUnlock(void)
107 {
108   __GLCthreadArea *area = NULL;
109 
110   area = GLC_GET_THREAD_AREA();
111   assert(area);
112 
113   area->lockState--;
114   if (!area->lockState)
115 #ifdef __WIN32__
116     LeaveCriticalSection(&__glcCommonArea.section);
117 #else
118     pthread_mutex_unlock(&__glcCommonArea.mutex);
119 #endif
120 }
121 
122 
123 
124 #if !defined(HAVE_TLS) && !defined(__WIN32__)
125 /* This function is called each time a pthread is cancelled or exits in order
126  * to free its specific area
127  */
__glcFreeThreadArea(void * keyValue)128 static void __glcFreeThreadArea(void *keyValue)
129 {
130   __GLCthreadArea *area = (__GLCthreadArea*)keyValue;
131   __GLCcontext *ctx = NULL;
132 
133   if (area) {
134     /* Release the context which is current to the thread, if any */
135     ctx = area->currentContext;
136     if (ctx)
137       ctx->isCurrent = GL_FALSE;
138     free(area); /* DO NOT use __glcFree() !!! */
139   }
140 }
141 #endif /* !HAVE_TLS && !__WIN32__ */
142 
143 
144 
145 /* This function is called when QuesoGLC is no longer needed.
146  */
147 #ifdef __GNUC__
fini(void)148 __attribute__((destructor)) void fini(void)
149 #else
150 void _fini(void)
151 #endif
152 {
153   FT_ListNode node = NULL;
154 #if 0
155   void *key = NULL;
156 #endif
157 
158   __glcLock();
159 
160   /* destroy remaining contexts */
161   node = __glcCommonArea.contextList.head;
162   while (node) {
163     FT_ListNode next = node->next;
164     __glcContextDestroy((__GLCcontext*)node);
165     node = next;
166   }
167 
168 #if FC_MINOR > 2
169   FcFini();
170 #endif
171 
172   __glcUnlock();
173 #ifdef __WIN32__
174   DeleteCriticalSection(&__glcCommonArea.section);
175 #else
176   pthread_mutex_destroy(&__glcCommonArea.mutex);
177 #endif
178 
179 #if 0
180   /* Destroy the thread local storage */
181   key = pthread_getspecific(__glcCommonArea.threadKey);
182   if (key)
183     __glcFreeThreadArea(key);
184 
185   pthread_key_delete(__glcCommonArea.threadKey);
186 #endif
187 }
188 
189 
190 
191 /* Routines for memory management of FreeType
192  * The memory manager of our FreeType library class uses the same memory
193  * allocation functions than QuesoGLC
194  */
__glcAllocFunc(FT_Memory GLC_UNUSED_ARG (inMemory),long inSize)195 static void* __glcAllocFunc(FT_Memory GLC_UNUSED_ARG(inMemory), long inSize)
196 {
197   return malloc(inSize);
198 }
199 
__glcFreeFunc(FT_Memory GLC_UNUSED_ARG (inMemory),void * inBlock)200 static void __glcFreeFunc(FT_Memory GLC_UNUSED_ARG(inMemory), void *inBlock)
201 {
202   free(inBlock);
203 }
204 
__glcReallocFunc(FT_Memory GLC_UNUSED_ARG (inMemory),long GLC_UNUSED_ARG (inCurSize),long inNewSize,void * inBlock)205 static void* __glcReallocFunc(FT_Memory GLC_UNUSED_ARG(inMemory),
206                               long GLC_UNUSED_ARG(inCurSize),
207                               long inNewSize, void* inBlock)
208 {
209   return realloc(inBlock, inNewSize);
210 }
211 
212 
213 
214 /* This function is called before any function of QuesoGLC
215  * is used. It reserves memory and initialiazes the library, hence the name.
216  */
217 #ifdef __GNUC__
init(void)218 __attribute__((constructor)) void init(void)
219 #else
220 void _init(void)
221 #endif
222 {
223 #if !defined(__WIN32__) && !defined(HAVE_TLS)
224   /* A temporary variable is used to store the PTHREAD_ONCE_INIT value because
225    * some platforms (namely Mac OSX) define PTHREAD_ONCE_INIT as a structure
226    * initialization "{.., ..}" which is not allowed to be used by C99 anywhere
227    * but at variables declaration.
228    */
229   pthread_once_t onceInit = PTHREAD_ONCE_INIT;
230 #endif
231 
232   /* Initialize fontconfig */
233   if (!FcInit())
234     goto FatalError;
235 
236   __glcCommonArea.versionMajor = 0;
237   __glcCommonArea.versionMinor = 2;
238 
239   /* Create the thread-local storage for GLC errors */
240 #ifdef __WIN32__
241   __glcCommonArea.threadKey = TlsAlloc();
242   if (__glcCommonArea.threadKey == 0xffffffff)
243     goto FatalError;
244   __glcCommonArea.__glcInitThreadOnce = 0;
245 #elif !defined(HAVE_TLS)
246   if (pthread_key_create(&__glcCommonArea.threadKey, __glcFreeThreadArea))
247     goto FatalError;
248   /* Now we can initialize our actual once_control variable by copying the value
249    * of the temporary variable onceInit.
250    */
251   __glcCommonArea.__glcInitThreadOnce = onceInit;
252 #endif
253 
254   __glcCommonArea.memoryManager.user = NULL;
255   __glcCommonArea.memoryManager.alloc = __glcAllocFunc;
256   __glcCommonArea.memoryManager.free = __glcFreeFunc;
257   __glcCommonArea.memoryManager.realloc = __glcReallocFunc;
258 
259   /* Initialize the list of context states */
260   __glcCommonArea.contextList.head = NULL;
261   __glcCommonArea.contextList.tail = NULL;
262 
263   /* Initialize the mutex for access to the contextList array */
264 #ifdef __WIN32__
265   InitializeCriticalSection(&__glcCommonArea.section);
266 #else
267   if (pthread_mutex_init(&__glcCommonArea.mutex, NULL))
268     goto FatalError;
269 #endif
270 
271   return;
272 
273  FatalError:
274   __glcRaiseError(GLC_RESOURCE_ERROR);
275 
276   /* Is there a better thing to do than that ? */
277   perror("GLC Fatal Error");
278   exit(-1);
279 }
280 
281 
282 
283 #if defined __WIN32__ && !defined __GNUC__
DllMain(HANDLE hinstDLL,DWORD dwReason,LPVOID lpvReserved)284 BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
285 {
286   switch(dwReason) {
287   case DLL_PROCESS_ATTACH:
288     _init();
289 	return TRUE;
290   case DLL_PROCESS_DETACH:
291     _fini();
292 	return TRUE;
293   }
294   return TRUE;
295 }
296 #endif
297 
298 
299 
300 /* Get the context state corresponding to a given context ID */
__glcGetContext(GLint inContext)301 static __GLCcontext* __glcGetContext(GLint inContext)
302 {
303   FT_ListNode node = NULL;
304 
305   __glcLock();
306   for (node = __glcCommonArea.contextList.head; node; node = node->next)
307     if (((__GLCcontext*)node)->id == inContext) break;
308 
309   __glcUnlock();
310 
311   return (__GLCcontext*)node;
312 }
313 
314 
315 
316 /** \ingroup global
317  *  This command checks whether \e inContext is the ID of one of the client's
318  *  GLC context and returns \b GLC_TRUE if and only if it is.
319  *  \param inContext The context ID to be tested
320  *  \return \b GL_TRUE if \e inContext is the ID of a GLC context,
321  *          \b GL_FALSE otherwise
322  *  \sa glcDeleteContext()
323  *  \sa glcGenContext()
324  *  \sa glcGetAllContexts()
325  *  \sa glcContext()
326  */
glcIsContext(GLint inContext)327 GLboolean APIENTRY glcIsContext(GLint inContext)
328 {
329   GLC_INIT_THREAD();
330 
331   return (__glcGetContext(inContext) ? GL_TRUE : GL_FALSE);
332 }
333 
334 
335 
336 /** \ingroup global
337  *  Returns the value of the issuing thread's current GLC context ID variable
338  *  \return The context ID of the current thread
339  *  \sa glcContext()
340  *  \sa glcDeleteContext()
341  *  \sa glcGenContext()
342  *  \sa glcGetAllContexts()
343  *  \sa glcIsContext()
344  */
glcGetCurrentContext(void)345 GLint APIENTRY glcGetCurrentContext(void)
346 {
347   __GLCcontext *ctx = NULL;
348 
349   GLC_INIT_THREAD();
350 
351   ctx = GLC_GET_CURRENT_CONTEXT();
352   if (!ctx)
353     return 0;
354   else
355     return ctx->id;
356 }
357 
358 
359 
360 /** \ingroup global
361  *  Marks for deletion the GLC context identified by \e inContext. If the
362  *  marked context is not current to any client thread, the command deletes
363  *  the marked context immediatly. Otherwise, the marked context will be
364  *  deleted during the execution of the next glcContext() command that causes
365  *  it not to be current to any client thread.
366  *
367  *  \note glcDeleteContext() does not destroy the GL objects associated with
368  *  the context \e inContext. Indeed for performance reasons, GLC does not keep
369  *  track of the GL context that contains the GL objects associated with the
370  *  the GLC context that is destroyed. Even if GLC would keep track of the GL
371  *  context, it could lead GLC to temporarily change the GL context, delete the
372  *  GL objects, then restore the correct GL context. In order not to adversely
373  *  impact the performance of most of programs, it is the responsability of the
374  *  user to call glcDeleteGLObjects() on a GLC context that is intended to be
375  *  destroyed.
376  *
377  *  The command raises \b GLC_PARAMETER_ERROR if \e inContext is not the ID of
378  *  one of the client's GLC contexts.
379  *  \param inContext The ID of the context to be deleted
380  *  \sa glcGetAllContexts()
381  *  \sa glcIsContext()
382  *  \sa glcContext()
383  *  \sa glcGetCurrentContext()
384  */
glcDeleteContext(GLint inContext)385 void APIENTRY glcDeleteContext(GLint inContext)
386 {
387   __GLCcontext *ctx = NULL;
388 
389   GLC_INIT_THREAD();
390 
391   /* Lock the "Common Area" in order to prevent race conditions. Indeed, we
392    * must prevent other threads to make current the context that we are
393    * destroying.
394    */
395   __glcLock();
396 
397   /* verify if the context exists */
398   ctx = __glcGetContext(inContext);
399 
400   if (!ctx) {
401     __glcRaiseError(GLC_PARAMETER_ERROR);
402     __glcUnlock();
403     return;
404   }
405 
406   if (ctx->isCurrent)
407     /* The context is current to a thread : just mark for deletion */
408     ctx->pendingDelete = GL_TRUE;
409   else {
410     /* Remove the context from the context list then destroy it */
411     FT_List_Remove(&__glcCommonArea.contextList, (FT_ListNode)ctx);
412     ctx->isInGlobalCommand = GL_TRUE;
413     __glcContextDestroy(ctx);
414   }
415 
416   __glcUnlock();
417 }
418 
419 
420 
421 /** \ingroup global
422  *  Assigns the value \e inContext to the issuing thread's current GLC context
423  *  ID variable. If another context is already current to the thread, no error
424  *  is generated but the context is released and the context identified by
425  *  \e inContext is made current to the thread.
426  *
427  *  Call \e glcContext with \e inContext set to zero to release a thread's
428  *  current context.
429  *
430  *  When a GLCcontext is made current to a thread, GLC issues the commands
431  *  \code
432  *  glGetString(GL_VERSION);
433  *  glGetString(GL_EXTENSIONS);
434  *  \endcode
435  *  and stores the returned strings. If there is no GL context current to the
436  *  thread, the result of the above GL commands is undefined and so is the
437  *  result of glcContext().
438  *
439  *  The command raises \b GLC_PARAMETER_ERROR if \e inContext is not zero
440  *  and is not the ID of one of the client's GLC contexts. \n
441  *  The command raises \b GLC_STATE_ERROR if \e inContext is the ID of a GLC
442  *  context that is current to a thread other than the issuing thread. \n
443  *  The command raises \b GLC_STATE_ERROR if the issuing thread is executing
444  *  a callback function that has been called from GLC.
445  *  \param inContext The ID of the context to be made current
446  *  \sa glcGetCurrentContext()
447  *  \sa glcDeleteContext()
448  *  \sa glcGenContext()
449  *  \sa glcGetAllContexts()
450  *  \sa glcIsContext()
451  */
glcContext(GLint inContext)452 void APIENTRY glcContext(GLint inContext)
453 {
454   __GLCcontext *currentContext = NULL;
455   __GLCcontext *ctx = NULL;
456   __GLCthreadArea *area = NULL;
457 
458   GLC_INIT_THREAD();
459 
460   if (inContext < 0) {
461     __glcRaiseError(GLC_PARAMETER_ERROR);
462     return;
463   }
464 
465   area = GLC_GET_THREAD_AREA();
466   assert(area);
467 
468   /* Lock the "Common Area" in order to prevent race conditions */
469   __glcLock();
470 
471   if (inContext) {
472     /* verify that the context exists */
473     ctx = __glcGetContext(inContext);
474 
475     if (!ctx) {
476       __glcRaiseError(GLC_PARAMETER_ERROR);
477       __glcUnlock();
478       return;
479     }
480 
481     /* Get the current context of the issuing thread */
482     currentContext = area->currentContext;
483 
484     /* Check if the issuing thread is executing a callback
485      * function that has been called from GLC
486      */
487     if (currentContext) {
488       if (currentContext->isInCallbackFunc) {
489 	__glcRaiseError(GLC_STATE_ERROR);
490 	__glcUnlock();
491 	return;
492       }
493     }
494 
495     /* Is the context already current to a thread ? */
496     if (ctx->isCurrent) {
497       /* If the context is current to another thread => ERROR ! */
498       if (!currentContext) {
499 	__glcRaiseError(GLC_STATE_ERROR);
500       }
501       else {
502 	if (currentContext->id != inContext)
503 	  __glcRaiseError(GLC_STATE_ERROR);
504       }
505 
506       /* If we get there, this means that the context 'inContext'
507        * is already current to one thread (whether it is the issuing thread
508        * or not) : there is nothing else to be done.
509        */
510       __glcUnlock();
511       return;
512     }
513 
514     /* Release old current context if any */
515     if (currentContext)
516       currentContext->isCurrent = GL_FALSE;
517 
518     /* Make the context current to the thread */
519     area->currentContext = ctx;
520     ctx->isCurrent = GL_TRUE;
521   }
522   else {
523     /* inContext is null, the current thread must release its context if any */
524 
525     /* Gets the current context state */
526     currentContext = area->currentContext;
527 
528     if (currentContext) {
529       /* Deassociate the context from the issuing thread */
530       area->currentContext = NULL;
531       /* Release the context */
532       currentContext->isCurrent = GL_FALSE;
533     }
534   }
535 
536   /* Execute pending deletion if any. Here, the variable name 'currentContext'
537    * is not appropriate any more : 'currentContext' used to be the current
538    * context but it has either been replaced by another one or it has been
539    * released.
540    */
541   if (currentContext) {
542     if (currentContext->pendingDelete) {
543       FT_List_Remove(&__glcCommonArea.contextList, (FT_ListNode)currentContext);
544       currentContext->isInGlobalCommand = GL_TRUE;
545       __glcContextDestroy(currentContext);
546     }
547   }
548 
549   __glcUnlock();
550 
551   /* If the issuing thread has released its context then there is no point to
552    * check for OpenGL extensions.
553    */
554   if (!inContext)
555     return;
556 
557   /* During its initialization, GLEW calls glGetString(GL_VERSION) and
558    * glGetString(GL_EXTENSIONS) so, not only glewInit() allows to determine the
559    * available extensions, but it also allows to be conformant with GLC specs
560    * which require to issue those GL commands.
561    *
562    * Notice however that some OpenGL implementations return a void pointer if
563    * the function glGetString() is called while no GL context is bound to the
564    * current thread. However this behaviour is not required by the GL specs, so
565    * those calls may just fail or lead to weird results or even crash the app
566    * (on Mac OSX). There is nothing that we can do against that : the GLC specs
567    * make it very clear that glGetString() is called by glcContext() and the
568    * QuesoGLC docs tell that the behaviour of GLC is undefined if no GL context
569    * is current while issuing GL commands.
570    */
571   if (glewInit() != GLEW_OK)
572     __glcRaiseError(GLC_RESOURCE_ERROR);
573 }
574 
575 
576 
577 /** \ingroup global
578  *  Generates a new GLC context and returns its ID.
579  *  \return The ID of the new context
580  *  \sa glcGetAllContexts()
581  *  \sa glcIsContext()
582  *  \sa glcContext()
583  *  \sa glcGetCurrentContext()
584  */
glcGenContext(void)585 GLint APIENTRY glcGenContext(void)
586 {
587   int newContext = 0;
588   __GLCcontext *ctx = NULL;
589   FT_ListNode node = NULL;
590 
591   GLC_INIT_THREAD();
592 
593   /* Create a new context */
594   ctx = __glcContextCreate(0);
595   if (!ctx)
596     return 0;
597 
598   /* Lock the "Common Area" in order to prevent race conditions */
599   __glcLock();
600 
601   /* Search for the first context ID that is unused */
602   if (!__glcCommonArea.contextList.tail)
603     newContext = 1;
604   else
605     newContext = ((__GLCcontext*)__glcCommonArea.contextList.tail)->id + 1;
606 
607   ctx->id = newContext;
608 
609   node = (FT_ListNode)ctx;
610   node->data = ctx;
611   FT_List_Add(&__glcCommonArea.contextList, node);
612 
613   __glcUnlock();
614 
615   return newContext;
616 }
617 
618 
619 
620 /** \ingroup global
621  *  Returns a zero terminated array of GLC context IDs that contains one entry
622  *  for each of the client's GLC contexts. GLC uses the ISO C library command
623  *  \c malloc to allocate the array. The client should use the ISO C library
624  *  command \c free to deallocate the array when it is no longer needed.
625  *  \return The pointer to the array of context IDs.
626  *  \sa glcContext()
627  *  \sa glcDeleteContext()
628  *  \sa glcGenContext()
629  *  \sa glcGetCurrentContext()
630  *  \sa glcIsContext()
631  */
glcGetAllContexts(void)632 GLint* APIENTRY glcGetAllContexts(void)
633 {
634   int count = 0;
635   GLint* contextArray = NULL;
636   FT_ListNode node = NULL;
637 
638   GLC_INIT_THREAD();
639 
640   /* Count the number of existing contexts (whether they are current to a
641    * thread or not).
642    */
643   __glcLock();
644   for (node = __glcCommonArea.contextList.head, count = 0; node;
645        node = node->next, count++);
646 
647   /* Allocate memory to store the array */
648   contextArray = (GLint *)__glcMalloc(sizeof(GLint) * (count+1));
649   if (!contextArray) {
650     __glcRaiseError(GLC_RESOURCE_ERROR);
651     __glcUnlock();
652     return NULL;
653   }
654 
655   /* Array must be null-terminated */
656   contextArray[count] = 0;
657 
658   /* Copy the context IDs to the array */
659   for (node = __glcCommonArea.contextList.tail; node;node = node->prev)
660     contextArray[--count] = ((__GLCcontext*)node)->id;
661 
662   __glcUnlock();
663 
664   return contextArray;
665 }
666 
667 
668 
669 /** \ingroup global
670  *  Retrieves the value of the issuing thread's GLC error code variable,
671  *  assigns the value \b GLC_NONE to that variable, and returns the retrieved
672  *  value.
673  *  \note In contrast to the GL function \c glGetError, \e glcGetError only
674  *  returns one error, not a list of errors.
675  *  \return An error code from the table below : \n\n
676  *   <center>
677  *   <table>
678  *     <caption>Error codes</caption>
679  *     <tr>
680  *       <td>Name</td> <td>Enumerant</td>
681  *     </tr>
682  *     <tr>
683  *       <td><b>GLC_NONE</b></td> <td>0x0000</td>
684  *     </tr>
685  *     <tr>
686  *       <td><b>GLC_PARAMETER_ERROR</b></td> <td>0x0040</td>
687  *     </tr>
688  *     <tr>
689  *       <td><b>GLC_RESOURCE_ERROR</b></td> <td>0x0041</td>
690  *     </tr>
691  *     <tr>
692  *       <td><b>GLC_STATE_ERROR</b></td> <td>0x0042</td>
693  *     </tr>
694  *   </table>
695  *   </center>
696  */
glcGetError(void)697 GLCenum APIENTRY glcGetError(void)
698 {
699   GLCenum error = GLC_NONE;
700   __GLCthreadArea * area = NULL;
701 
702   GLC_INIT_THREAD();
703 
704   area = GLC_GET_THREAD_AREA();
705   assert(area);
706 
707   error = area->errorState;
708   __glcRaiseError(GLC_NONE);
709   return error;
710 }
711