/*
 * Copyright 2008 George Sapountzis
 *
 * 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 and this permission notice (including the next
 * paragraph) 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
 * 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#if defined(GLX_DIRECT_RENDERING) && !defined(GLX_USE_APPLEGL)

#include <X11/Xlib.h>
#include "glxclient.h"
#include <dlfcn.h>
#include "dri_common.h"
#include "drisw_priv.h"
#include <X11/extensions/shmproto.h>
#include <assert.h>

static Bool
XCreateGCs(struct drisw_drawable * pdp,
           Display * dpy, XID drawable, int visualid)
{
   XGCValues gcvalues;
   long visMask;
   XVisualInfo visTemp;
   int num_visuals;

   /* create GC's */
   pdp->gc = XCreateGC(dpy, drawable, 0, NULL);
   pdp->swapgc = XCreateGC(dpy, drawable, 0, NULL);

   gcvalues.function = GXcopy;
   gcvalues.graphics_exposures = False;
   XChangeGC(dpy, pdp->gc, GCFunction, &gcvalues);
   XChangeGC(dpy, pdp->swapgc, GCFunction, &gcvalues);
   XChangeGC(dpy, pdp->swapgc, GCGraphicsExposures, &gcvalues);

   /* visual */
   visTemp.visualid = visualid;
   visMask = VisualIDMask;
   pdp->visinfo = XGetVisualInfo(dpy, visMask, &visTemp, &num_visuals);

   if (!pdp->visinfo || num_visuals == 0)
      return False;

   return True;
}

static int xshm_error = 0;
static int xshm_opcode = -1;

/**
 * Catches potential Xlib errors.
 */
static int
handle_xerror(Display *dpy, XErrorEvent *event)
{
   (void) dpy;

   assert(xshm_opcode != -1);
   if (event->request_code != xshm_opcode)
      return 0;

   xshm_error = event->error_code;
   return 0;
}

static Bool
XCreateDrawable(struct drisw_drawable * pdp, int shmid, Display * dpy)
{
   if (pdp->ximage) {
      XDestroyImage(pdp->ximage);
      pdp->ximage = NULL;
   }

   if (!xshm_error && shmid >= 0) {
      pdp->shminfo.shmid = shmid;
      pdp->ximage = XShmCreateImage(dpy,
                                    pdp->visinfo->visual,
                                    pdp->visinfo->depth,
                                    ZPixmap,              /* format */
                                    NULL,                 /* data */
                                    &pdp->shminfo,        /* shminfo */
                                    0, 0);                /* width, height */
      if (pdp->ximage != NULL) {
         int (*old_handler)(Display *, XErrorEvent *);

         /* dispatch pending errors */
         XSync(dpy, False);

         old_handler = XSetErrorHandler(handle_xerror);
         /* This may trigger the X protocol error we're ready to catch: */
         XShmAttach(dpy, &pdp->shminfo);
         XSync(dpy, False);

         if (xshm_error) {
         /* we are on a remote display, this error is normal, don't print it */
            XDestroyImage(pdp->ximage);
            pdp->ximage = NULL;
         }

         (void) XSetErrorHandler(old_handler);
      }
   }

   if (pdp->ximage == NULL) {
      pdp->shminfo.shmid = -1;
      pdp->ximage = XCreateImage(dpy,
                                 pdp->visinfo->visual,
                                 pdp->visinfo->depth,
                                 ZPixmap, 0,             /* format, offset */
                                 NULL,                   /* data */
                                 0, 0,                   /* width, height */
                                 32,                     /* bitmap_pad */
                                 0);                     /* bytes_per_line */
   }

  /**
   * swrast does not handle 24-bit depth with 24 bpp, so let X do the
   * the conversion for us.
   */
  if (pdp->ximage->bits_per_pixel == 24)
     pdp->ximage->bits_per_pixel = 32;

   return True;
}

static void
XDestroyDrawable(struct drisw_drawable * pdp, Display * dpy, XID drawable)
{
   if (pdp->ximage)
      XDestroyImage(pdp->ximage);

   if (pdp->shminfo.shmid > 0)
      XShmDetach(dpy, &pdp->shminfo);

   free(pdp->visinfo);

   XFreeGC(dpy, pdp->gc);
   XFreeGC(dpy, pdp->swapgc);
}

/**
 * swrast loader functions
 */

static void
swrastGetDrawableInfo(__DRIdrawable * draw,
                      int *x, int *y, int *w, int *h,
                      void *loaderPrivate)
{
   struct drisw_drawable *pdp = loaderPrivate;
   __GLXDRIdrawable *pdraw = &(pdp->base);
   Display *dpy = pdraw->psc->dpy;
   Drawable drawable;

   Window root;
   unsigned uw, uh, bw, depth;

   drawable = pdraw->xDrawable;

   XGetGeometry(dpy, drawable, &root, x, y, &uw, &uh, &bw, &depth);
   *w = uw;
   *h = uh;
}

/**
 * Align renderbuffer pitch.
 *
 * This should be chosen by the driver and the loader (libGL, xserver/glx)
 * should use the driver provided pitch.
 *
 * It seems that the xorg loader (that is the xserver loading swrast_dri for
 * indirect rendering, not client-side libGL) requires that the pitch is
 * exactly the image width padded to 32 bits. XXX
 *
 * The above restriction can probably be overcome by using ScratchPixmap and
 * CopyArea in the xserver, similar to ShmPutImage, and setting the width of
 * the scratch pixmap to 'pitch / cpp'.
 */
static inline int
bytes_per_line(unsigned pitch_bits, unsigned mul)
{
   unsigned mask = mul - 1;

   return ((pitch_bits + mask) & ~mask) / 8;
}

static void
swrastXPutImage(__DRIdrawable * draw, int op,
                int srcx, int srcy, int x, int y,
                int w, int h, int stride,
                int shmid, char *data, void *loaderPrivate)
{
   struct drisw_drawable *pdp = loaderPrivate;
   __GLXDRIdrawable *pdraw = &(pdp->base);
   Display *dpy = pdraw->psc->dpy;
   Drawable drawable;
   XImage *ximage;
   GC gc;

   if (!pdp->ximage || shmid != pdp->shminfo.shmid) {
      if (!XCreateDrawable(pdp, shmid, dpy))
         return;
   }

   switch (op) {
   case __DRI_SWRAST_IMAGE_OP_DRAW:
      gc = pdp->gc;
      break;
   case __DRI_SWRAST_IMAGE_OP_SWAP:
      gc = pdp->swapgc;
      break;
   default:
      return;
   }

   drawable = pdraw->xDrawable;
   ximage = pdp->ximage;
   ximage->bytes_per_line = stride ? stride : bytes_per_line(w * ximage->bits_per_pixel, 32);
   ximage->data = data;

   if (pdp->shminfo.shmid >= 0) {
      ximage->width = ximage->bytes_per_line / ((ximage->bits_per_pixel + 7)/ 8);
      ximage->height = h;
      XShmPutImage(dpy, drawable, gc, ximage, srcx, srcy, x, y, w, h, False);
      XSync(dpy, False);
   } else {
      ximage->width = w;
      ximage->height = h;
      XPutImage(dpy, drawable, gc, ximage, srcx, srcy, x, y, w, h);
   }
   ximage->data = NULL;
}

static void
swrastPutImageShm(__DRIdrawable * draw, int op,
                  int x, int y, int w, int h, int stride,
                  int shmid, char *shmaddr, unsigned offset,
                  void *loaderPrivate)
{
   struct drisw_drawable *pdp = loaderPrivate;

   pdp->shminfo.shmaddr = shmaddr;
   swrastXPutImage(draw, op, 0, 0, x, y, w, h, stride, shmid,
                   shmaddr + offset, loaderPrivate);
}

static void
swrastPutImageShm2(__DRIdrawable * draw, int op,
                   int x, int y,
                   int w, int h, int stride,
		   int shmid, char *shmaddr, unsigned offset,
		   void *loaderPrivate)
{
   struct drisw_drawable *pdp = loaderPrivate;

   pdp->shminfo.shmaddr = shmaddr;
   swrastXPutImage(draw, op, x, 0, x, y, w, h, stride, shmid,
                   shmaddr + offset, loaderPrivate);
}

static void
swrastPutImage2(__DRIdrawable * draw, int op,
                int x, int y, int w, int h, int stride,
                char *data, void *loaderPrivate)
{
   swrastXPutImage(draw, op, 0, 0, x, y, w, h, stride, -1,
                   data, loaderPrivate);
}

static void
swrastPutImage(__DRIdrawable * draw, int op,
               int x, int y, int w, int h,
               char *data, void *loaderPrivate)
{
   swrastXPutImage(draw, op, 0, 0, x, y, w, h, 0, -1,
                   data, loaderPrivate);
}

static void
swrastGetImage2(__DRIdrawable * read,
                int x, int y, int w, int h, int stride,
                char *data, void *loaderPrivate)
{
   struct drisw_drawable *prp = loaderPrivate;
   __GLXDRIdrawable *pread = &(prp->base);
   Display *dpy = pread->psc->dpy;
   Drawable readable;
   XImage *ximage;

   if (!prp->ximage || prp->shminfo.shmid >= 0) {
      if (!XCreateDrawable(prp, -1, dpy))
         return;
   }

   readable = pread->xDrawable;

   ximage = prp->ximage;
   ximage->data = data;
   ximage->width = w;
   ximage->height = h;
   ximage->bytes_per_line = stride ? stride : bytes_per_line(w * ximage->bits_per_pixel, 32);

   XGetSubImage(dpy, readable, x, y, w, h, ~0L, ZPixmap, ximage, 0, 0);

   ximage->data = NULL;
}

static void
swrastGetImage(__DRIdrawable * read,
               int x, int y, int w, int h,
               char *data, void *loaderPrivate)
{
   swrastGetImage2(read, x, y, w, h, 0, data, loaderPrivate);
}

static void
swrastGetImageShm(__DRIdrawable * read,
                  int x, int y, int w, int h,
                  int shmid, void *loaderPrivate)
{
   struct drisw_drawable *prp = loaderPrivate;
   __GLXDRIdrawable *pread = &(prp->base);
   Display *dpy = pread->psc->dpy;
   Drawable readable;
   XImage *ximage;

   if (!prp->ximage || shmid != prp->shminfo.shmid) {
      if (!XCreateDrawable(prp, shmid, dpy))
         return;
   }
   readable = pread->xDrawable;

   ximage = prp->ximage;
   ximage->data = prp->shminfo.shmaddr; /* no offset */
   ximage->width = w;
   ximage->height = h;
   ximage->bytes_per_line = bytes_per_line(w * ximage->bits_per_pixel, 32);

   XShmGetImage(dpy, readable, ximage, x, y, ~0L);
}

static const __DRIswrastLoaderExtension swrastLoaderExtension_shm = {
   .base = {__DRI_SWRAST_LOADER, 5 },

   .getDrawableInfo     = swrastGetDrawableInfo,
   .putImage            = swrastPutImage,
   .getImage            = swrastGetImage,
   .putImage2           = swrastPutImage2,
   .getImage2           = swrastGetImage2,
   .putImageShm         = swrastPutImageShm,
   .getImageShm         = swrastGetImageShm,
   .putImageShm2        = swrastPutImageShm2,
};

static const __DRIextension *loader_extensions_shm[] = {
   &swrastLoaderExtension_shm.base,
   NULL
};

static const __DRIswrastLoaderExtension swrastLoaderExtension = {
   .base = {__DRI_SWRAST_LOADER, 3 },

   .getDrawableInfo     = swrastGetDrawableInfo,
   .putImage            = swrastPutImage,
   .getImage            = swrastGetImage,
   .putImage2           = swrastPutImage2,
   .getImage2           = swrastGetImage2,
};

static const __DRIextension *loader_extensions_noshm[] = {
   &swrastLoaderExtension.base,
   NULL
};

/**
 * GLXDRI functions
 */

static void
drisw_destroy_context(struct glx_context *context)
{
   struct drisw_context *pcp = (struct drisw_context *) context;
   struct drisw_screen *psc = (struct drisw_screen *) context->psc;

   driReleaseDrawables(&pcp->base);

   free((char *) context->extensions);

   (*psc->core->destroyContext) (pcp->driContext);

   free(pcp);
}

static int
drisw_bind_context(struct glx_context *context, struct glx_context *old,
		   GLXDrawable draw, GLXDrawable read)
{
   struct drisw_context *pcp = (struct drisw_context *) context;
   struct drisw_screen *psc = (struct drisw_screen *) pcp->base.psc;
   struct drisw_drawable *pdraw, *pread;

   pdraw = (struct drisw_drawable *) driFetchDrawable(context, draw);
   pread = (struct drisw_drawable *) driFetchDrawable(context, read);

   driReleaseDrawables(&pcp->base);

   if ((*psc->core->bindContext) (pcp->driContext,
                                  pdraw ? pdraw->driDrawable : NULL,
                                  pread ? pread->driDrawable : NULL))
      return Success;

   return GLXBadContext;
}

static void
drisw_unbind_context(struct glx_context *context, struct glx_context *new)
{
   struct drisw_context *pcp = (struct drisw_context *) context;
   struct drisw_screen *psc = (struct drisw_screen *) pcp->base.psc;

   (*psc->core->unbindContext) (pcp->driContext);
}

static void
drisw_bind_tex_image(Display * dpy,
		    GLXDrawable drawable,
		    int buffer, const int *attrib_list)
{
   struct glx_context *gc = __glXGetCurrentContext();
   struct drisw_context *pcp = (struct drisw_context *) gc;
   __GLXDRIdrawable *base = GetGLXDRIDrawable(dpy, drawable);
   struct drisw_drawable *pdraw = (struct drisw_drawable *) base;
   struct drisw_screen *psc;

   __glXInitialize(dpy);

   if (pdraw != NULL) {
      psc = (struct drisw_screen *) base->psc;

      if (!psc->texBuffer)
         return;

      if (psc->texBuffer->base.version >= 2 &&
        psc->texBuffer->setTexBuffer2 != NULL) {
	      (*psc->texBuffer->setTexBuffer2) (pcp->driContext,
					   pdraw->base.textureTarget,
					   pdraw->base.textureFormat,
					   pdraw->driDrawable);
      }
      else {
	      (*psc->texBuffer->setTexBuffer) (pcp->driContext,
					  pdraw->base.textureTarget,
					  pdraw->driDrawable);
      }
   }
}

static void
drisw_release_tex_image(Display * dpy, GLXDrawable drawable, int buffer)
{
   struct glx_context *gc = __glXGetCurrentContext();
   struct drisw_context *pcp = (struct drisw_context *) gc;
   __GLXDRIdrawable *base = GetGLXDRIDrawable(dpy, drawable);
   struct glx_display *dpyPriv = __glXInitialize(dpy);
   struct drisw_drawable *pdraw = (struct drisw_drawable *) base;
   struct drisw_screen *psc;

   if (dpyPriv != NULL && pdraw != NULL) {
      psc = (struct drisw_screen *) base->psc;

      if (!psc->texBuffer)
         return;

      if (psc->texBuffer->base.version >= 3 &&
          psc->texBuffer->releaseTexBuffer != NULL) {
         (*psc->texBuffer->releaseTexBuffer) (pcp->driContext,
                                           pdraw->base.textureTarget,
                                           pdraw->driDrawable);
      }
   }
}

static const struct glx_context_vtable drisw_context_vtable = {
   .destroy             = drisw_destroy_context,
   .bind                = drisw_bind_context,
   .unbind              = drisw_unbind_context,
   .wait_gl             = NULL,
   .wait_x              = NULL,
   .use_x_font          = DRI_glXUseXFont,
   .bind_tex_image      = drisw_bind_tex_image,
   .release_tex_image   = drisw_release_tex_image,
   .get_proc_address    = NULL,
};

static struct glx_context *
drisw_create_context(struct glx_screen *base,
		     struct glx_config *config_base,
		     struct glx_context *shareList, int renderType)
{
   struct drisw_context *pcp, *pcp_shared;
   __GLXDRIconfigPrivate *config = (__GLXDRIconfigPrivate *) config_base;
   struct drisw_screen *psc = (struct drisw_screen *) base;
   __DRIcontext *shared = NULL;

   if (!psc->base.driScreen)
      return NULL;

   /* Check the renderType value */
   if (!validate_renderType_against_config(config_base, renderType))
       return NULL;

   if (shareList) {
      /* If the shareList context is not a DRISW context, we cannot possibly
       * create a DRISW context that shares it.
       */
      if (shareList->vtable->destroy != drisw_destroy_context) {
	 return NULL;
      }

      pcp_shared = (struct drisw_context *) shareList;
      shared = pcp_shared->driContext;
   }

   pcp = calloc(1, sizeof *pcp);
   if (pcp == NULL)
      return NULL;

   if (!glx_context_init(&pcp->base, &psc->base, &config->base)) {
      free(pcp);
      return NULL;
   }

   pcp->base.renderType = renderType;

   pcp->driContext =
      (*psc->core->createNewContext) (psc->driScreen,
				      config->driConfig, shared, pcp);
   if (pcp->driContext == NULL) {
      free(pcp);
      return NULL;
   }

   pcp->base.vtable = &drisw_context_vtable;

   return &pcp->base;
}

static struct glx_context *
drisw_create_context_attribs(struct glx_screen *base,
			     struct glx_config *config_base,
			     struct glx_context *shareList,
			     unsigned num_attribs,
			     const uint32_t *attribs,
			     unsigned *error)
{
   struct drisw_context *pcp, *pcp_shared;
   __GLXDRIconfigPrivate *config = (__GLXDRIconfigPrivate *) config_base;
   struct drisw_screen *psc = (struct drisw_screen *) base;
   __DRIcontext *shared = NULL;

   uint32_t minor_ver;
   uint32_t major_ver;
   uint32_t renderType;
   uint32_t flags;
   unsigned api;
   int reset;
   int release;
   uint32_t ctx_attribs[2 * 5];
   unsigned num_ctx_attribs = 0;

   if (!psc->base.driScreen)
      return NULL;

   if (psc->swrast->base.version < 3)
      return NULL;

   /* Remap the GLX tokens to DRI2 tokens.
    */
   if (!dri2_convert_glx_attribs(num_attribs, attribs,
                                 &major_ver, &minor_ver, &renderType, &flags,
                                 &api, &reset, &release, error))
      return NULL;

   if (!dri2_check_no_error(flags, shareList, major_ver, error))
      return NULL;

   /* Check the renderType value */
   if (!validate_renderType_against_config(config_base, renderType)) {
       return NULL;
   }

   if (reset != __DRI_CTX_RESET_NO_NOTIFICATION)
      return NULL;

   if (release != __DRI_CTX_RELEASE_BEHAVIOR_FLUSH &&
       release != __DRI_CTX_RELEASE_BEHAVIOR_NONE)
      return NULL;

   if (shareList) {
      pcp_shared = (struct drisw_context *) shareList;
      shared = pcp_shared->driContext;
   }

   pcp = calloc(1, sizeof *pcp);
   if (pcp == NULL)
      return NULL;

   if (!glx_context_init(&pcp->base, &psc->base, config_base)) {
      free(pcp);
      return NULL;
   }

   ctx_attribs[num_ctx_attribs++] = __DRI_CTX_ATTRIB_MAJOR_VERSION;
   ctx_attribs[num_ctx_attribs++] = major_ver;
   ctx_attribs[num_ctx_attribs++] = __DRI_CTX_ATTRIB_MINOR_VERSION;
   ctx_attribs[num_ctx_attribs++] = minor_ver;
   if (release != __DRI_CTX_RELEASE_BEHAVIOR_FLUSH) {
       ctx_attribs[num_ctx_attribs++] = __DRI_CTX_ATTRIB_RELEASE_BEHAVIOR;
       ctx_attribs[num_ctx_attribs++] = release;
   }

   if (flags != 0) {
      ctx_attribs[num_ctx_attribs++] = __DRI_CTX_ATTRIB_FLAGS;

      /* The current __DRI_CTX_FLAG_* values are identical to the
       * GLX_CONTEXT_*_BIT values.
       */
      ctx_attribs[num_ctx_attribs++] = flags;

      if (flags & __DRI_CTX_FLAG_NO_ERROR)
         pcp->base.noError = GL_TRUE;
   }

   pcp->base.renderType = renderType;

   pcp->driContext =
      (*psc->swrast->createContextAttribs) (psc->driScreen,
					    api,
					    config ? config->driConfig : 0,
					    shared,
					    num_ctx_attribs / 2,
					    ctx_attribs,
					    error,
					    pcp);
   if (pcp->driContext == NULL) {
      free(pcp);
      return NULL;
   }

   pcp->base.vtable = &drisw_context_vtable;

   return &pcp->base;
}

static void
driswDestroyDrawable(__GLXDRIdrawable * pdraw)
{
   struct drisw_drawable *pdp = (struct drisw_drawable *) pdraw;
   struct drisw_screen *psc = (struct drisw_screen *) pdp->base.psc;

   (*psc->core->destroyDrawable) (pdp->driDrawable);

   XDestroyDrawable(pdp, pdraw->psc->dpy, pdraw->drawable);
   free(pdp);
}

static __GLXDRIdrawable *
driswCreateDrawable(struct glx_screen *base, XID xDrawable,
		    GLXDrawable drawable, struct glx_config *modes)
{
   struct drisw_drawable *pdp;
   __GLXDRIconfigPrivate *config = (__GLXDRIconfigPrivate *) modes;
   struct drisw_screen *psc = (struct drisw_screen *) base;
   Bool ret;
   const __DRIswrastExtension *swrast = psc->swrast;

   pdp = calloc(1, sizeof(*pdp));
   if (!pdp)
      return NULL;

   pdp->base.xDrawable = xDrawable;
   pdp->base.drawable = drawable;
   pdp->base.psc = &psc->base;

   ret = XCreateGCs(pdp, psc->base.dpy, xDrawable, modes->visualID);
   if (!ret) {
      free(pdp);
      return NULL;
   }

   /* Create a new drawable */
   pdp->driDrawable =
      (*swrast->createNewDrawable) (psc->driScreen, config->driConfig, pdp);

   if (!pdp->driDrawable) {
      XDestroyDrawable(pdp, psc->base.dpy, xDrawable);
      free(pdp);
      return NULL;
   }

   pdp->base.destroyDrawable = driswDestroyDrawable;

   return &pdp->base;
}

static int64_t
driswSwapBuffers(__GLXDRIdrawable * pdraw,
                 int64_t target_msc, int64_t divisor, int64_t remainder,
                 Bool flush)
{
   struct drisw_drawable *pdp = (struct drisw_drawable *) pdraw;
   struct drisw_screen *psc = (struct drisw_screen *) pdp->base.psc;

   (void) target_msc;
   (void) divisor;
   (void) remainder;

   if (flush) {
      glFlush();
   }

   (*psc->core->swapBuffers) (pdp->driDrawable);

   return 0;
}

static void
driswCopySubBuffer(__GLXDRIdrawable * pdraw,
                   int x, int y, int width, int height, Bool flush)
{
   struct drisw_drawable *pdp = (struct drisw_drawable *) pdraw;
   struct drisw_screen *psc = (struct drisw_screen *) pdp->base.psc;

   if (flush) {
      glFlush();
   }

   (*psc->copySubBuffer->copySubBuffer) (pdp->driDrawable,
					    x, y, width, height);
}

static void
driswDestroyScreen(struct glx_screen *base)
{
   struct drisw_screen *psc = (struct drisw_screen *) base;

   /* Free the direct rendering per screen data */
   (*psc->core->destroyScreen) (psc->driScreen);
   driDestroyConfigs(psc->driver_configs);
   psc->driScreen = NULL;
   if (psc->driver)
      dlclose(psc->driver);
   free(psc);
}

#define SWRAST_DRIVER_NAME "swrast"

static const struct glx_screen_vtable drisw_screen_vtable = {
   .create_context         = drisw_create_context,
   .create_context_attribs = drisw_create_context_attribs,
   .query_renderer_integer = drisw_query_renderer_integer,
   .query_renderer_string  = drisw_query_renderer_string,
};

static void
driswBindExtensions(struct drisw_screen *psc, const __DRIextension **extensions)
{
   int i;

   __glXEnableDirectExtension(&psc->base, "GLX_SGI_make_current_read");

   if (psc->swrast->base.version >= 3) {
      __glXEnableDirectExtension(&psc->base, "GLX_ARB_create_context");
      __glXEnableDirectExtension(&psc->base, "GLX_ARB_create_context_profile");

      /* DRISW version >= 2 implies support for OpenGL ES.
       */
      __glXEnableDirectExtension(&psc->base,
				 "GLX_EXT_create_context_es_profile");
      __glXEnableDirectExtension(&psc->base,
				 "GLX_EXT_create_context_es2_profile");
   }

   if (psc->copySubBuffer)
      __glXEnableDirectExtension(&psc->base, "GLX_MESA_copy_sub_buffer");      

   /* FIXME: Figure out what other extensions can be ported here from dri2. */
   for (i = 0; extensions[i]; i++) {
      if ((strcmp(extensions[i]->name, __DRI_TEX_BUFFER) == 0)) {
	 psc->texBuffer = (__DRItexBufferExtension *) extensions[i];
	 __glXEnableDirectExtension(&psc->base, "GLX_EXT_texture_from_pixmap");
      }
      /* DRISW version 3 is also required because GLX_MESA_query_renderer
       * requires GLX_ARB_create_context_profile.
       */
      if (psc->swrast->base.version >= 3
          && strcmp(extensions[i]->name, __DRI2_RENDERER_QUERY) == 0) {
         psc->rendererQuery = (__DRI2rendererQueryExtension *) extensions[i];
         __glXEnableDirectExtension(&psc->base, "GLX_MESA_query_renderer");
      }
      if (strcmp(extensions[i]->name, __DRI2_FLUSH_CONTROL) == 0) {
	  __glXEnableDirectExtension(&psc->base,
				     "GLX_ARB_context_flush_control");
      }
   }
}

static int
check_xshm(Display *dpy)
{
   int (*old_handler)(Display *, XErrorEvent *);

   int ignore;
   XShmSegmentInfo info = { 0, };

   if (!XQueryExtension(dpy, "MIT-SHM", &xshm_opcode, &ignore, &ignore))
      return False;

   old_handler = XSetErrorHandler(handle_xerror);
   XShmDetach(dpy, &info);
   XSync(dpy, False);
   (void) XSetErrorHandler(old_handler);

   /* BadRequest means we're a remote client. If we were local we'd
    * expect BadValue since 'info' has an invalid segment name.
    */
   if (xshm_error == BadRequest)
      return False;

   xshm_error = 0;
   return True;
}

static struct glx_screen *
driswCreateScreen(int screen, struct glx_display *priv)
{
   __GLXDRIscreen *psp;
   const __DRIconfig **driver_configs;
   const __DRIextension **extensions;
   struct drisw_screen *psc;
   struct glx_config *configs = NULL, *visuals = NULL;
   int i;
   const __DRIextension **loader_extensions_local;

   psc = calloc(1, sizeof *psc);
   if (psc == NULL)
      return NULL;

   if (!glx_screen_init(&psc->base, screen, priv)) {
      free(psc);
      return NULL;
   }

   extensions = driOpenDriver(SWRAST_DRIVER_NAME, &psc->driver);
   if (extensions == NULL)
      goto handle_error;

   if (!check_xshm(psc->base.dpy))
      loader_extensions_local = loader_extensions_noshm;
   else
      loader_extensions_local = loader_extensions_shm;

   for (i = 0; extensions[i]; i++) {
      if (strcmp(extensions[i]->name, __DRI_CORE) == 0)
	 psc->core = (__DRIcoreExtension *) extensions[i];
      if (strcmp(extensions[i]->name, __DRI_SWRAST) == 0)
	 psc->swrast = (__DRIswrastExtension *) extensions[i];
      if (strcmp(extensions[i]->name, __DRI_COPY_SUB_BUFFER) == 0)
	 psc->copySubBuffer = (__DRIcopySubBufferExtension *) extensions[i];
   }

   if (psc->core == NULL || psc->swrast == NULL) {
      ErrorMessageF("core dri extension not found\n");
      goto handle_error;
   }

   if (psc->swrast->base.version >= 4) {
      psc->driScreen =
         psc->swrast->createNewScreen2(screen, loader_extensions_local,
                                       extensions,
                                       &driver_configs, psc);
   } else {
      psc->driScreen =
         psc->swrast->createNewScreen(screen, loader_extensions_local,
                                      &driver_configs, psc);
   }
   if (psc->driScreen == NULL) {
      ErrorMessageF("failed to create dri screen\n");
      goto handle_error;
   }

   extensions = psc->core->getExtensions(psc->driScreen);
   driswBindExtensions(psc, extensions);

   /* XXX: is this no good for upstream as it would change the fbconfigs seen
      by DRI2/3, it should work on a copy of the fbconfigs instead ??? */
   for (struct glx_config *m = psc->base.configs; m; m = m->next) {
      /* We will ignore any server performance caveats, since they don't
         affect swrast performance */
      m->visualRating = GLX_DONT_CARE;

      /* swrast doesn't provide any aux buffers, but XWin may (with some
         drivers) provide them for all configs, so set the number to zero.

         This could potentially create duplicate fbconfigs, but that isn't
         forbidden. */
      m->numAuxBuffers = 0;
   }

   for (struct glx_config *m = psc->base.visuals; m; m = m->next) {
      m->visualRating = GLX_DONT_CARE;
      m->numAuxBuffers = 0;
   }

   for (struct glx_config *m = psc->base.configs; m; m = m->next) {

   }
   for (struct glx_config *m = psc->base.visuals; m; m = m->next) {

   }

   configs = driConvertConfigs(psc->core, psc->base.configs, driver_configs);
   visuals = driConvertConfigs(psc->core, psc->base.visuals, driver_configs);

   if (!configs || !visuals) {
       ErrorMessageF("No matching fbConfigs or visuals found\n");
       goto handle_error;
   }

   glx_config_destroy_list(psc->base.configs);
   psc->base.configs = configs;
   glx_config_destroy_list(psc->base.visuals);
   psc->base.visuals = visuals;

   psc->driver_configs = driver_configs;

   psc->base.vtable = &drisw_screen_vtable;
   psp = &psc->vtable;
   psc->base.driScreen = psp;
   psp->destroyScreen = driswDestroyScreen;
   psp->createDrawable = driswCreateDrawable;
   psp->swapBuffers = driswSwapBuffers;

   if (psc->copySubBuffer)
      psp->copySubBuffer = driswCopySubBuffer;

   return &psc->base;

 handle_error:
   if (configs)
       glx_config_destroy_list(configs);
   if (visuals)
       glx_config_destroy_list(visuals);
   if (psc->driScreen)
       psc->core->destroyScreen(psc->driScreen);
   psc->driScreen = NULL;

   if (psc->driver)
      dlclose(psc->driver);
   glx_screen_cleanup(&psc->base);
   free(psc);

   CriticalErrorMessageF("failed to load driver: %s\n", SWRAST_DRIVER_NAME);

   return NULL;
}

/* Called from __glXFreeDisplayPrivate.
 */
static void
driswDestroyDisplay(__GLXDRIdisplay * dpy)
{
   free(dpy);
}

/*
 * Allocate, initialize and return a __DRIdisplayPrivate object.
 * This is called from __glXInitialize() when we are given a new
 * display pointer.
 */
_X_HIDDEN __GLXDRIdisplay *
driswCreateDisplay(Display * dpy)
{
   struct drisw_display *pdpyp;

   pdpyp = malloc(sizeof *pdpyp);
   if (pdpyp == NULL)
      return NULL;

   pdpyp->base.destroyDisplay = driswDestroyDisplay;
   pdpyp->base.createScreen = driswCreateScreen;

   return &pdpyp->base;
}

#endif /* GLX_DIRECT_RENDERING */
