/*
 * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
 * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice including the dates of first publication and
 * either this permission notice or a reference to
 * http://oss.sgi.com/projects/FreeB/
 * shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS 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
 * SILICON GRAPHICS, INC. 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of Silicon Graphics, Inc.
 * shall not be used in advertising or otherwise to promote the sale, use or
 * other dealings in this Software without prior written authorization from
 * Silicon Graphics, Inc.
 */

/**
 * \file glxcurrent.c
 * Client-side GLX interface for current context management.
 */

#include "glxclient.h"
#include "glapi.h"
#include "indirect_init.h"

#ifdef GLX_DIRECT_RENDERING
#include "xf86dri.h"
#endif

/*
** We setup some dummy structures here so that the API can be used
** even if no context is current.
*/

static GLubyte dummyBuffer[__GLX_BUFFER_LIMIT_SIZE];

/*
** Dummy context used by small commands when there is no current context.
** All the
** gl and glx entry points are designed to operate as nop's when using
** the dummy context structure.
*/
static __GLXcontext dummyContext = {
   &dummyBuffer[0],
   &dummyBuffer[0],
   &dummyBuffer[0],
   &dummyBuffer[__GLX_BUFFER_LIMIT_SIZE],
   sizeof(dummyBuffer),
};


/*
** All indirect rendering contexts will share the same indirect dispatch table.
*/
static __GLapi *IndirectAPI = NULL;


/*
 * Current context management and locking
 */

#if defined( PTHREADS )

_X_HIDDEN pthread_mutex_t __glXmutex = PTHREAD_MUTEX_INITIALIZER;

# if defined( GLX_USE_TLS )

/**
 * Per-thread GLX context pointer.
 *
 * \c __glXSetCurrentContext is written is such a way that this pointer can
 * \b never be \c NULL.  This is important!  Because of this
 * \c __glXGetCurrentContext can be implemented as trivial macro.
 */
__thread void *__glX_tls_Context __attribute__ ((tls_model("initial-exec")))
   = &dummyContext;

_X_HIDDEN void
__glXSetCurrentContext(__GLXcontext * c)
{
   __glX_tls_Context = (c != NULL) ? c : &dummyContext;
}

# else

static pthread_once_t once_control = PTHREAD_ONCE_INIT;

/**
 * Per-thread data key.
 *
 * Once \c init_thread_data has been called, the per-thread data key will
 * take a value of \c NULL.  As each new thread is created the default
 * value, in that thread, will be \c NULL.
 */
static pthread_key_t ContextTSD;

/**
 * Initialize the per-thread data key.
 *
 * This function is called \b exactly once per-process (not per-thread!) to
 * initialize the per-thread data key.  This is ideally done using the
 * \c pthread_once mechanism.
 */
static void
init_thread_data(void)
{
   if (pthread_key_create(&ContextTSD, NULL) != 0) {
      perror("pthread_key_create");
      exit(-1);
   }
}

_X_HIDDEN void
__glXSetCurrentContext(__GLXcontext * c)
{
   pthread_once(&once_control, init_thread_data);
   pthread_setspecific(ContextTSD, c);
}

_X_HIDDEN __GLXcontext *
__glXGetCurrentContext(void)
{
   void *v;

   pthread_once(&once_control, init_thread_data);

   v = pthread_getspecific(ContextTSD);
   return (v == NULL) ? &dummyContext : (__GLXcontext *) v;
}

# endif /* defined( GLX_USE_TLS ) */

#elif defined( THREADS )

#error Unknown threading method specified.

#else

/* not thread safe */
_X_HIDDEN __GLXcontext *__glXcurrentContext = &dummyContext;

#endif


_X_HIDDEN void
__glXSetCurrentContextNull(void)
{
   __glXSetCurrentContext(&dummyContext);
#ifdef GLX_DIRECT_RENDERING
   _glapi_set_dispatch(NULL);   /* no-op functions */
   _glapi_set_context(NULL);
#endif
}


/************************************************************************/

PUBLIC GLXContext
glXGetCurrentContext(void)
{
   GLXContext cx = __glXGetCurrentContext();

   if (cx == &dummyContext) {
      return NULL;
   }
   else {
      return cx;
   }
}

PUBLIC GLXDrawable
glXGetCurrentDrawable(void)
{
   GLXContext gc = __glXGetCurrentContext();
   return gc->currentDrawable;
}


/************************************************************************/

/**
 * Sends a GLX protocol message to the specified display to make the context
 * and the drawables current.
 *
 * \param dpy     Display to send the message to.
 * \param opcode  Major opcode value for the display.
 * \param gc_id   Context tag for the context to be made current.
 * \param draw    Drawable ID for the "draw" drawable.
 * \param read    Drawable ID for the "read" drawable.
 * \param reply   Space to store the X-server's reply.
 *
 * \warning
 * This function assumes that \c dpy is locked with \c LockDisplay on entry.
 */
static Bool
SendMakeCurrentRequest(Display * dpy, CARD8 opcode,
                       GLXContextID gc_id, GLXContextTag gc_tag,
                       GLXDrawable draw, GLXDrawable read,
                       xGLXMakeCurrentReply * reply)
{
   Bool ret;


   LockDisplay(dpy);

   if (draw == read) {
      xGLXMakeCurrentReq *req;

      GetReq(GLXMakeCurrent, req);
      req->reqType = opcode;
      req->glxCode = X_GLXMakeCurrent;
      req->drawable = draw;
      req->context = gc_id;
      req->oldContextTag = gc_tag;
   }
   else {
      __GLXdisplayPrivate *priv = __glXInitialize(dpy);

      /* If the server can support the GLX 1.3 version, we should
       * perfer that.  Not only that, some servers support GLX 1.3 but
       * not the SGI extension.
       */

      if ((priv->majorVersion > 1) || (priv->minorVersion >= 3)) {
         xGLXMakeContextCurrentReq *req;

         GetReq(GLXMakeContextCurrent, req);
         req->reqType = opcode;
         req->glxCode = X_GLXMakeContextCurrent;
         req->drawable = draw;
         req->readdrawable = read;
         req->context = gc_id;
         req->oldContextTag = gc_tag;
      }
      else {
         xGLXVendorPrivateWithReplyReq *vpreq;
         xGLXMakeCurrentReadSGIReq *req;

         GetReqExtra(GLXVendorPrivateWithReply,
                     sz_xGLXMakeCurrentReadSGIReq -
                     sz_xGLXVendorPrivateWithReplyReq, vpreq);
         req = (xGLXMakeCurrentReadSGIReq *) vpreq;
         req->reqType = opcode;
         req->glxCode = X_GLXVendorPrivateWithReply;
         req->vendorCode = X_GLXvop_MakeCurrentReadSGI;
         req->drawable = draw;
         req->readable = read;
         req->context = gc_id;
         req->oldContextTag = gc_tag;
      }
   }

   ret = _XReply(dpy, (xReply *) reply, 0, False);

   UnlockDisplay(dpy);
   SyncHandle();

   return ret;
}


#ifdef GLX_DIRECT_RENDERING
static __GLXDRIdrawable *
FetchDRIDrawable(Display * dpy, GLXDrawable glxDrawable, GLXContext gc)
{
   __GLXdisplayPrivate *const priv = __glXInitialize(dpy);
   __GLXDRIdrawable *pdraw;
   __GLXscreenConfigs *psc;

   if (priv == NULL)
      return NULL;

   psc = &priv->screenConfigs[gc->screen];
   if (psc->drawHash == NULL)
      return NULL;

   if (__glxHashLookup(psc->drawHash, glxDrawable, (void *) &pdraw) == 0)
      return pdraw;

   pdraw = psc->driScreen->createDrawable(psc, glxDrawable,
                                          glxDrawable, gc->mode);
   if (__glxHashInsert(psc->drawHash, glxDrawable, pdraw)) {
      (*pdraw->destroyDrawable) (pdraw);
      return NULL;
   }

   return pdraw;
}
#endif /* GLX_DIRECT_RENDERING */

static void
__glXGenerateError(Display * dpy, GLXContext gc, XID resource,
                   BYTE errorCode, CARD16 minorCode)
{
   xError error;

   error.errorCode = errorCode;
   error.resourceID = resource;
   error.sequenceNumber = dpy->request;
   error.type = X_Error;
   error.majorCode = gc->majorOpcode;
   error.minorCode = minorCode;
   _XError(dpy, &error);
}

/**
 * Make a particular context current.
 *
 * \note This is in this file so that it can access dummyContext.
 */
static Bool
MakeContextCurrent(Display * dpy, GLXDrawable draw,
                   GLXDrawable read, GLXContext gc)
{
   xGLXMakeCurrentReply reply;
   const GLXContext oldGC = __glXGetCurrentContext();
   const CARD8 opcode = __glXSetupForCommand(dpy);
   const CARD8 oldOpcode = ((gc == oldGC) || (oldGC == &dummyContext))
      ? opcode : __glXSetupForCommand(oldGC->currentDpy);
   Bool bindReturnValue;
   __GLXattribute *state;

   if (!opcode || !oldOpcode) {
      return GL_FALSE;
   }

   /* Make sure that the new context has a nonzero ID.  In the request,
    * a zero context ID is used only to mean that we bind to no current
    * context.
    */
   if ((gc != NULL) && (gc->xid == None)) {
      return GL_FALSE;
   }

   if (gc == NULL && (draw != None || read != None)) {
      __glXGenerateError(dpy, gc, (draw != None) ? draw : read,
                         BadMatch, X_GLXMakeContextCurrent);
      return False;
   }
   if (gc != NULL && (draw == None || read == None)) {
      __glXGenerateError(dpy, gc, None, BadMatch, X_GLXMakeContextCurrent);
      return False;
   }

   _glapi_check_multithread();

   if (gc != NULL && gc->thread_id != 0 && gc->thread_id != _glthread_GetID()) {
      __glXGenerateError(dpy, gc, gc->xid,
                         BadAccess, X_GLXMakeContextCurrent);
      return False;
   }

#ifdef GLX_DIRECT_RENDERING
   /* Bind the direct rendering context to the drawable */
   if (gc && gc->driContext) {
      __GLXDRIdrawable *pdraw = FetchDRIDrawable(dpy, draw, gc);
      __GLXDRIdrawable *pread = FetchDRIDrawable(dpy, read, gc);

      if ((pdraw == NULL) || (pread == NULL)) {
         __glXGenerateError(dpy, gc, (pdraw == NULL) ? draw : read,
                            GLXBadDrawable, X_GLXMakeContextCurrent);
         return False;
      }

      bindReturnValue =
         (gc->driContext->bindContext) (gc->driContext, pdraw, pread);
   }
   else if (!gc && oldGC && oldGC->driContext) {
      bindReturnValue = True;
   }
   else
#endif
   {
      /* Send a glXMakeCurrent request to bind the new context. */
      bindReturnValue =
         SendMakeCurrentRequest(dpy, opcode, gc ? gc->xid : None,
                                ((dpy != oldGC->currentDpy)
                                 || oldGC->isDirect)
                                ? None : oldGC->currentContextTag, draw, read,
                                &reply);
   }


   if (!bindReturnValue) {
      return False;
   }

#ifdef GLX_DIRECT_RENDERING
   if ((dpy != oldGC->currentDpy || (gc && gc->driContext)) &&
       !oldGC->isDirect && oldGC != &dummyContext) {
#else
   if ((dpy != oldGC->currentDpy) && oldGC != &dummyContext) {
#endif
      xGLXMakeCurrentReply dummy_reply;

      /* We are either switching from one dpy to another and have to
       * send a request to the previous dpy to unbind the previous
       * context, or we are switching away from a indirect context to
       * a direct context and have to send a request to the dpy to
       * unbind the previous context.
       */
      (void) SendMakeCurrentRequest(oldGC->currentDpy, oldOpcode, None,
                                    oldGC->currentContextTag, None, None,
                                    &dummy_reply);
   }
#ifdef GLX_DIRECT_RENDERING
   else if (oldGC->driContext && oldGC != gc) {
      oldGC->driContext->unbindContext(oldGC->driContext);
   }
#endif


   /* Update our notion of what is current */
   __glXLock();
   if (gc == oldGC) {
      /* Even though the contexts are the same the drawable might have
       * changed.  Note that gc cannot be the dummy, and that oldGC
       * cannot be NULL, therefore if they are the same, gc is not
       * NULL and not the dummy.
       */
      gc->currentDrawable = draw;
      gc->currentReadable = read;
   }
   else {
      if (oldGC != &dummyContext) {
         /* Old current context is no longer current to anybody */
         oldGC->currentDpy = 0;
         oldGC->currentDrawable = None;
         oldGC->currentReadable = None;
         oldGC->currentContextTag = 0;
         oldGC->thread_id = 0;

         if (oldGC->xid == None) {
            /* We are switching away from a context that was
             * previously destroyed, so we need to free the memory
             * for the old handle.
             */
#ifdef GLX_DIRECT_RENDERING
            /* Destroy the old direct rendering context */
            if (oldGC->driContext) {
               oldGC->driContext->destroyContext(oldGC->driContext,
                                                 oldGC->psc,
                                                 oldGC->createDpy);
               oldGC->driContext = NULL;
            }
#endif
            __glXFreeContext(oldGC);
         }
      }
      if (gc) {
         __glXSetCurrentContext(gc);

         gc->currentDpy = dpy;
         gc->currentDrawable = draw;
         gc->currentReadable = read;
         gc->thread_id = _glthread_GetID();

#ifdef GLX_DIRECT_RENDERING
         if (!gc->driContext) {
#endif
            if (!IndirectAPI)
               IndirectAPI = __glXNewIndirectAPI();
            _glapi_set_dispatch(IndirectAPI);

            state = (__GLXattribute *) (gc->client_state_private);

            gc->currentContextTag = reply.contextTag;
            if (state->array_state == NULL) {
               (void) glGetString(GL_EXTENSIONS);
               (void) glGetString(GL_VERSION);
               __glXInitVertexArrayState(gc);
            }
#ifdef GLX_DIRECT_RENDERING
         }
         else {
            gc->currentContextTag = -1;
         }
#endif
      }
      else {
         __glXSetCurrentContextNull();
      }
   }
   __glXUnlock();
   return GL_TRUE;
}


PUBLIC Bool
glXMakeCurrent(Display * dpy, GLXDrawable draw, GLXContext gc)
{
   return MakeContextCurrent(dpy, draw, draw, gc);
}

PUBLIC
GLX_ALIAS(Bool, glXMakeCurrentReadSGI,
          (Display * dpy, GLXDrawable d, GLXDrawable r, GLXContext ctx),
          (dpy, d, r, ctx), MakeContextCurrent)

PUBLIC
GLX_ALIAS(Bool, glXMakeContextCurrent,
          (Display * dpy, GLXDrawable d, GLXDrawable r,
           GLXContext ctx), (dpy, d, r, ctx), MakeContextCurrent)
