/* Window-specific OpenGL functions implementation.

     Copyright (c) 1999 Lionel Ulmer
*/

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include "wine/exception.h"

#include "wine/debug.h"
#include "gdi.h"
#include "windef.h"
#include "winerror.h"
#include "wine/wine_gl.h"
#include "x11drv.h"
#include "x11font.h"
#include "msvcrt/excpt.h"

#include "wgl.h"
#include "opengl_ext.h"

#include "ddrawi.h"
#include "d3dhal.h"

#include "ts_xutil.h"

#define USE_D3D_HAL

extern void WINAPI __wine_get_hal(LPDDRAWI_DIRECTDRAW_GBL *gbl, LPDDHAL_GETDRIVERINFO *ddgdi);

WINE_DEFAULT_DEBUG_CHANNEL(opengl);

static GLXContext default_cx = NULL;
static Display *default_display = NULL;
static ULONG_PTR dwhDefContext = 0;
static char *wgl_exts = NULL;

static LPDDRAWI_DIRECTDRAW_GBL dd_gbl;
static LPDDHAL_CALLBACKS dd_cbs;
static LPD3DHAL_GLOBALDRIVERDATA d3d_hal_data;
static LPD3DHAL_CALLBACKS d3d_hal_cbs1;
static LPD3DHAL_CALLBACKS2 d3d_hal_cbs2;
static LPDDHAL_GETDRIVERINFO dd_get_driver_info = NULL;

typedef struct wine_glcontext {
  HDC hdc;
  GLXContext ctx;
  Display *display;
  XVisualInfo *vis;
  ULONG_PTR dwhContext;
  GLint draw_buffer;
  DDRAWI_DDRAWSURFACE_LCL lcl;
  DDRAWI_DDRAWSURFACE_MORE more;
  LPDDRAWI_DDRAWSURFACE_GBL_MORE gmore;
  DDRAWI_DDRAWSURFACE_GBL gbl;
  DDRAWI_DDRAWSURFACE_GBL_MORE gbl_more;
  struct wine_glcontext *next;
  struct wine_glcontext *prev;
} Wine_GLContext;
static Wine_GLContext *context_array;

static inline Display *get_display(HDC dc)
{
  Display *display = NULL;
  enum x11drv_escape_codes code = X11DRV_GET_DISPLAY;
  ExtEscape(dc, X11DRV_ESCAPE,
            sizeof(code), (LPCSTR)&code,
            sizeof(display), (LPSTR)&display);
  return display;
}

static inline Drawable get_drawable(HDC dc)
{
  Drawable drawable = None;
  enum x11drv_escape_codes code = X11DRV_GET_DRAWABLE;
  ExtEscape(dc, X11DRV_ESCAPE,
            sizeof(code), (LPCSTR)&code,
            sizeof(drawable), (LPSTR)&drawable);
  return drawable;
}

static inline Font get_xfont(HDC dc)
{
  Font font = None;
  enum x11drv_escape_codes code = X11DRV_GET_FONT;
  ExtEscape(dc, X11DRV_ESCAPE,
            sizeof(code), (LPCSTR)&code,
            sizeof(font), (LPSTR)&font);
  return font;
}

static inline Wine_GLContext *get_context_from_GLXContext(GLXContext ctx) {
  Wine_GLContext *ret = context_array;
  while (ret != NULL) if (ctx == ret->ctx) break; else ret = ret->next;
  return ret;
}

static inline void free_context(Wine_GLContext *context) {
  if (context->next != NULL) context->next->prev = context->prev;
  if (context->prev != NULL) context->prev->next = context->next;
  else                       context_array = context->next;

  HeapFree(GetProcessHeap(), 0, context);
}

static inline Wine_GLContext *alloc_context(void) {
  Wine_GLContext *ret;

  ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(Wine_GLContext));
  ret->next = context_array;
  if (context_array != NULL) context_array->prev = ret;
  else                       context_array = ret;

  ret->lcl.lpGbl = &ret->gbl;
  ret->lcl.lpSurfMore = &ret->more;
  ret->gmore = &ret->gbl_more;

  return ret;
}

static inline Wine_GLContext *check_context(Wine_GLContext *context) {
  Wine_GLContext *ret = context_array;
  while (ret != NULL) if (context == ret) break; else ret = ret->next;
  return ret;
}

static void init_context(Wine_GLContext *context, HDC hdc)
{
  context->gbl_more.hKernelSurface = GetCurrentObject(hdc, OBJ_BITMAP);
  if (context->gbl_more.hKernelSurface) {
    /* rendering to a bitmap */
    context->lcl.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
    context->more.lpDDRAWReserved = 0;
    if (!context->draw_buffer)
      context->draw_buffer = GL_FRONT;
  } else {
    /* rendering to window */
    context->lcl.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE;
    context->more.lpDDRAWReserved = (LPVOID)WindowFromDC(hdc);
    if (!context->draw_buffer)
      context->draw_buffer = GL_BACK;
  }
}

static BOOL make_context_current(Wine_GLContext *context, HDC hdc);

static BOOL create_context(Wine_GLContext *context, Wine_GLContext *share) {
  if (dd_gbl) {
    D3DHAL_CONTEXTCREATEDATA data;

    data.u1.lpDDLcl = NULL;
    data.u2.lpDDSLcl = &context->lcl;
    data.u3.lpDDSZLcl = NULL;
    data.u4.dwPID = (DWORD)-1;
    data.dwhContext = share ? share->dwhContext : 0;
    d3d_hal_cbs1->ContextCreate(&data);
    if (data.ddrval != S_OK) {
      ERR("could not create context\n");
      return FALSE;
    }
    context->dwhContext = data.dwhContext;

    ENTER_GL();
    context->ctx = glXGetCurrentContext();
    LEAVE_GL();

    make_context_current(NULL, 0);
    context->draw_buffer = 0;
  } else {
    GLXContext share_ctx = share ? share->ctx : NULL;
    ENTER_GL();
    context->ctx = glXCreateContext(context->display, context->vis, share_ctx, True);
    LEAVE_GL();
  }
  return TRUE;
}

static void destroy_context(Wine_GLContext *context) {
  if (context->dwhContext) {
    D3DHAL_CONTEXTDESTROYDATA data;
    data.dwhContext = context->dwhContext;
    d3d_hal_cbs1->ContextDestroy(&data);
  }
  else if (context->ctx) {
    ENTER_GL();
    glXDestroyContext(context->display, context->ctx);
    LEAVE_GL();
  }
  context->ctx = NULL;
  context->dwhContext = 0;
}

static BOOL make_context_current(Wine_GLContext *context, HDC hdc) {
  BOOL ret = TRUE;
  if (dd_gbl) {
    D3DHAL_SETRENDERTARGETDATA data;
    GLXContext gl_ctx;
    Wine_GLContext *old_ctx;

    ENTER_GL();
    gl_ctx = glXGetCurrentContext();
    old_ctx = get_context_from_GLXContext(gl_ctx);
    if (old_ctx) glGetIntegerv(GL_DRAW_BUFFER, &old_ctx->draw_buffer);
    LEAVE_GL();

    if (context && context->dwhContext) {
      init_context(context, hdc);
      data.dwhContext = context->dwhContext;
      data.u1.lpDDSLcl = &context->lcl;
      data.u2.lpDDSZLcl = NULL;
    } else {
      data.dwhContext = 0;
      data.u1.lpDDSLcl = NULL;
      data.u2.lpDDSZLcl = NULL;
    }
    d3d_hal_cbs2->SetRenderTarget(&data);
    ret = (data.ddrval == S_OK);

    ENTER_GL();
    if (ret && context && context->dwhContext)
      glDrawBuffer(context->draw_buffer);
    LEAVE_GL();
  }
  else {
    ENTER_GL();
    if (context && context->ctx) {
      ret = glXMakeCurrent(context->display,
			   get_drawable(hdc),
			   context->ctx);
    } else {
      ret = glXMakeCurrent(default_display,
			   None,
			   NULL);
    }
    LEAVE_GL();
  }
  return ret;
}


/***********************************************************************
 *		wglCreateContext (OPENGL32.@)
 */
HGLRC WINAPI wglCreateContext(HDC hdc)
{
  XVisualInfo *vis;
  Wine_GLContext *ret;
  int num;
  XVisualInfo template;
  Display *display = get_display(hdc);

  TRACE("(%08x)\n", hdc);

  /* First, get the visual in use by the X11DRV */
  template.visualid = GetPropA( GetDesktopWindow(), "__wine_x11_visual_id" );
  vis = TSXGetVisualInfo(display, VisualIDMask, &template, &num);

  if (vis == NULL) {
    ERR("NULL visual !!!\n");
    /* Need to set errors here */
    return NULL;
  }

  /* The context will be allocated in the wglMakeCurrent call */
  ENTER_GL();
  ret = alloc_context();
  LEAVE_GL();
  ret->hdc = hdc;
  ret->vis = vis;
  ret->display = display;

  init_context(ret, hdc);

  TRACE(" creating context %p (GL context creation delayed)\n", ret);
  return (HGLRC) ret;
}

/***********************************************************************
 *		wglCreateLayerContext (OPENGL32.@)
 */
HGLRC WINAPI wglCreateLayerContext(HDC hdc,
				   int iLayerPlane) {
  FIXME("(%08x,%d): stub !\n", hdc, iLayerPlane);

  return NULL;
}

/***********************************************************************
 *		wglCopyContext (OPENGL32.@)
 */
BOOL WINAPI wglCopyContext(HGLRC hglrcSrc,
			   HGLRC hglrcDst,
			   UINT mask) {
  FIXME("(%p,%p,%d)\n", hglrcSrc, hglrcDst, mask);

  return FALSE;
}

/***********************************************************************
 *		wglDeleteContext (OPENGL32.@)
 */
BOOL WINAPI wglDeleteContext(HGLRC hglrc) {
  Wine_GLContext *ctx = (Wine_GLContext *) hglrc;
  BOOL ret;

  TRACE("(%p)\n", hglrc);

  ENTER_GL();
  /* A game (Half Life not to name it) deletes twice the same context.
   * Check for this. */
  if (check_context(ctx)) {
    destroy_context(ctx);
    free_context(ctx);
    ret = TRUE;
  } else {
    WARN("invalid context\n");
    SetLastError(ERROR_INVALID_HANDLE);
    ret = FALSE;
  }
  LEAVE_GL();

  return ret;
}

/***********************************************************************
 *		wglDescribeLayerPlane (OPENGL32.@)
 */
BOOL WINAPI wglDescribeLayerPlane(HDC hdc,
				  int iPixelFormat,
				  int iLayerPlane,
				  UINT nBytes,
				  LPLAYERPLANEDESCRIPTOR plpd) {
  FIXME("(%08x,%d,%d,%d,%p)\n", hdc, iPixelFormat, iLayerPlane, nBytes, plpd);

  return FALSE;
}

/***********************************************************************
 *		wglGetCurrentContext (OPENGL32.@)
 */
HGLRC WINAPI wglGetCurrentContext(void) {
  GLXContext gl_ctx;
  Wine_GLContext *ret;

  TRACE("()\n");

  ENTER_GL();
  gl_ctx = glXGetCurrentContext();
  ret = get_context_from_GLXContext(gl_ctx);
  LEAVE_GL();

  TRACE(" returning %p (GL context %p)\n", ret, gl_ctx);

  return ret;
}

/***********************************************************************
 *		wglGetCurrentDC (OPENGL32.@)
 */
HDC WINAPI wglGetCurrentDC(void) {
  GLXContext gl_ctx;
  Wine_GLContext *ret;

  TRACE("()\n");

  ENTER_GL();
  gl_ctx = glXGetCurrentContext();
  ret = get_context_from_GLXContext(gl_ctx);
  LEAVE_GL();

  if (ret) {
    TRACE(" returning %08x (GL context %p - Wine context %p)\n", ret->hdc, gl_ctx, ret);
    return ret->hdc;
  } else {
    TRACE(" no Wine context found for GLX context %p\n", gl_ctx);
    return 0;
  }
}

/***********************************************************************
 *		wglGetLayerPaletteEntries (OPENGL32.@)
 */
int WINAPI wglGetLayerPaletteEntries(HDC hdc,
				     int iLayerPlane,
				     int iStart,
				     int cEntries,
				     const COLORREF *pcr) {
  FIXME("(): stub !\n");

  return 0;
}

/***********************************************************************
 *		wglGetProcAddress (OPENGL32.@)
 */
static int compar(const void *elt_a, const void *elt_b) {
  return strcmp(((OpenGL_extension *) elt_a)->name,
		((OpenGL_extension *) elt_b)->name);
}

void* WINAPI wglGetProcAddress(LPCSTR  lpszProc) {
  void *local_func;
  static HMODULE hm = 0;
  OpenGL_extension  ext;
  OpenGL_extension *ext_ret;


  TRACE("(%s)\n", lpszProc);

  if (hm == 0)
      hm = GetModuleHandleA("opengl32");

  /* First, look if it's not already defined in the 'standard' OpenGL functions */
  if ((local_func = GetProcAddress(hm, lpszProc)) != NULL) {
    TRACE(" found function in 'standard' OpenGL functions (%p)\n", local_func);
    return local_func;
  }

  /* After that, search in the thunks to find the real name of the extension */
  ext.name = (char *) lpszProc;
  ext_ret = (OpenGL_extension *) bsearch(&ext, extension_registry,
					 extension_registry_size, sizeof(OpenGL_extension), compar);

  if (ext_ret == NULL) {
    /* Some sanity checks :-) */
    ENTER_GL();
    if (glXGetProcAddressARB(lpszProc) != NULL) {
      LEAVE_GL();
      ERR("Extension %s defined in the OpenGL library but NOT in opengl_ext.c... Please report!\n", lpszProc);
      return NULL;
    }
    LEAVE_GL();

    WARN("Did not find extension %s in either Wine or your OpenGL library.\n", lpszProc);
    return NULL;
  } else {
    /* After that, look at the extensions defined in the Linux OpenGL library */
    if ((local_func = glXGetProcAddressARB(ext_ret->glx_name)) == NULL) {
      char buf[256];
      void *ret = NULL;

      /* Remove the 3 last letters (EXT, ARB, ...).

	 I know that some extensions have more than 3 letters (MESA, NV,
	 INTEL, ...), but this is only a stop-gap measure to fix buggy
	 OpenGL drivers (moreover, it is only useful for old 1.0 apps
	 that query the glBindTextureEXT extension).
      */
      strncpy(buf, ext_ret->glx_name, strlen(ext_ret->glx_name) - 3);
      buf[strlen(ext_ret->glx_name) - 3] = '\0';
      TRACE(" extension not found in the Linux OpenGL library, checking against libGL bug with %s..\n", buf);

      ret = GetProcAddress(hm, buf);
      if (ret != NULL) {
	TRACE(" found function in main OpenGL library (%p) !\n", ret);
      } else {
	WARN("Did not find function %s (%s) in your OpenGL library !\n", lpszProc, ext_ret->glx_name);
      }

      return ret;
    } else {
      TRACE(" returning function  (%p)\n", ext_ret->func);
      *(ext_ret->func_ptr) = local_func;

      return ext_ret->func;
    }
  }
}

/***********************************************************************
 *		wglMakeCurrent (OPENGL32.@)
 */
BOOL WINAPI wglMakeCurrent(HDC hdc,
			   HGLRC hglrc) {
  BOOL ret;

  TRACE("(%08x,%p)\n", hdc, hglrc);

  if (hglrc == NULL) {
    ret = make_context_current(NULL, 0);
  } else {
    if (!hdc) {
      ERR("Null DC !!!\n");
      ret = FALSE;
    } else {
      Wine_GLContext *ctx = (Wine_GLContext *) hglrc;

      if (ctx->ctx == NULL) {
	create_context(ctx, NULL);
	TRACE(" created a delayed OpenGL context (%p)\n", ctx->ctx);
      }

      ret = make_context_current(ctx, hdc);
    }
  }
  TRACE(" returning %s\n", (ret ? "True" : "False"));
  return ret;
}

/***********************************************************************
 *		wglRealizeLayerPalette (OPENGL32.@)
 */
BOOL WINAPI wglRealizeLayerPalette(HDC hdc,
				   int iLayerPlane,
				   BOOL bRealize) {
  FIXME("()\n");

  return FALSE;
}

/***********************************************************************
 *		wglSetLayerPaletteEntries (OPENGL32.@)
 */
int WINAPI wglSetLayerPaletteEntries(HDC hdc,
				     int iLayerPlane,
				     int iStart,
				     int cEntries,
				     const COLORREF *pcr) {
  FIXME("(): stub !\n");

  return 0;
}

/***********************************************************************
 *		wglShareLists (OPENGL32.@)
 */
BOOL WINAPI wglShareLists(HGLRC hglrc1,
			  HGLRC hglrc2) {
  Wine_GLContext *org  = (Wine_GLContext *) hglrc1;
  Wine_GLContext *dest = (Wine_GLContext *) hglrc2;

  TRACE("(%p, %p)\n", org, dest);

  if (dest->ctx != NULL) {
    ERR("Could not share display lists, context already created !\n");
    return FALSE;
  } else {
    if (org->ctx == NULL) {
      create_context(org, NULL);
      TRACE(" created a delayed OpenGL context (%p) for Wine context %p\n", org->ctx, org);
    }

    /* Create the destination context with display lists shared */
    create_context(dest, org);
    TRACE(" created a delayed OpenGL context (%p) for Wine context %p sharing lists with OpenGL ctx %p\n", dest->ctx, dest, org->ctx);
  }

  return TRUE;
}

/***********************************************************************
 *		wglSwapLayerBuffers (OPENGL32.@)
 */
BOOL WINAPI wglSwapLayerBuffers(HDC hdc,
				UINT fuPlanes) {
  TRACE("(%08x, %08x)\n", hdc, fuPlanes);

  if (fuPlanes & WGL_SWAP_MAIN_PLANE) {
    if (!SwapBuffers(hdc)) return FALSE;
    fuPlanes &= ~WGL_SWAP_MAIN_PLANE;
  }

  if (fuPlanes) {
    WARN("Following layers unhandled : %08x\n", fuPlanes);
  }

  return TRUE;
}

/***********************************************************************
 *		wglUseFontBitmapsA (OPENGL32.@)
 */
BOOL WINAPI wglUseFontBitmapsA(HDC hdc,
			       DWORD first,
			       DWORD count,
			       DWORD listBase) {
  Font fid = get_xfont(hdc);

  TRACE("(%08x, %ld, %ld, %ld) using font %ld\n", hdc, first, count, listBase, fid);

  if (fid == 0) {
    /* We are running using client-side rendering fonts... */
    GLYPHMETRICS gm;
    static const MAT2 id = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
    int glyph;
    int size = 0;
    void *bitmap = NULL, *gl_bitmap = NULL;
    int org_alignment;

    ENTER_GL();
    glGetIntegerv(GL_UNPACK_ALIGNMENT, &org_alignment);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    LEAVE_GL();

    for (glyph = first; glyph < first + count; glyph++) {
      int needed_size = GetGlyphOutlineA(hdc, glyph, GGO_BITMAP, &gm, 0, NULL, &id);
      int height, width_int;

      if (needed_size == GDI_ERROR) goto error;
      if (needed_size > size) {
	size = needed_size;
	if (bitmap) HeapFree(GetProcessHeap(), 0, bitmap);
	if (gl_bitmap) HeapFree(GetProcessHeap(), 0, gl_bitmap);
	bitmap = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
	gl_bitmap = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
      }
      if (GetGlyphOutlineA(hdc, glyph, GGO_BITMAP, &gm, size, bitmap, &id) == GDI_ERROR) goto error;
      if (TRACE_ON(opengl)) {
	unsigned int height, width, bitmask;
	unsigned char *bitmap_ = (unsigned char *) bitmap;

	DPRINTF("Glyph : %d\n", glyph);
	DPRINTF("  - bbox : %d x %d\n", gm.gmBlackBoxX, gm.gmBlackBoxY);
	DPRINTF("  - origin : (%ld , %ld)\n", gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y);
	DPRINTF("  - increment : %d - %d\n", gm.gmCellIncX, gm.gmCellIncY);
	DPRINTF("  - size : %d\n", needed_size);
	DPRINTF("  - bitmap : \n");
	for (height = 0; height < gm.gmBlackBoxY; height++) {
	  DPRINTF("      ");
	  for (width = 0, bitmask = 0x80; width < gm.gmBlackBoxX; width++, bitmask >>= 1) {
	    if (bitmask == 0) {
	      bitmap_ += 1;
	      bitmask = 0x80;
	    }
	    if (*bitmap_ & bitmask)
	      DPRINTF("*");
	    else
	      DPRINTF(" ");
	  }
	  bitmap_ += (4 - (((unsigned int) bitmap_) & 0x03));
	  DPRINTF("\n");
	}
      }

      /* For some obscure reasons, I seem to need to rotate the glyph for OpenGL to be happy.
	 As Wine does not seem to support the MAT2 field, I need to do it myself.... */
      width_int = (gm.gmBlackBoxX + 31) / 32;
      for (height = 0; height < gm.gmBlackBoxY; height++) {
	int width;
	for (width = 0; width < width_int; width++) {
	  ((int *) gl_bitmap)[(gm.gmBlackBoxY - height - 1) * width_int + width] =
	    ((int *) bitmap)[height * width_int + width];
	}
      }

      ENTER_GL();
      glNewList(listBase++, GL_COMPILE);
      glBitmap(gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x, gm.gmBlackBoxY - gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY, gl_bitmap);
      glEndList();
      LEAVE_GL();
    }

    ENTER_GL();
    glPixelStorei(GL_UNPACK_ALIGNMENT, org_alignment);
    LEAVE_GL();

    if (bitmap) HeapFree(GetProcessHeap(), 0, bitmap);
    if (gl_bitmap) HeapFree(GetProcessHeap(), 0, gl_bitmap);
    return TRUE;

  error:
    ENTER_GL();
    glPixelStorei(GL_UNPACK_ALIGNMENT, org_alignment);
    LEAVE_GL();

    if (bitmap) HeapFree(GetProcessHeap(), 0, bitmap);
    if (gl_bitmap) HeapFree(GetProcessHeap(), 0, gl_bitmap);
    return FALSE;
  }

  ENTER_GL();
  /* I assume that the glyphs are at the same position for X and for Windows */
  glXUseXFont(fid, first, count, listBase);
  LEAVE_GL();
  return TRUE;
}

/***********************************************************************
 *		wglUseFontOutlinesA (OPENGL32.@)
 */
BOOL WINAPI wglUseFontOutlinesA(HDC hdc,
				DWORD first,
				DWORD count,
				DWORD listBase,
				FLOAT deviation,
				FLOAT extrusion,
				int format,
				LPGLYPHMETRICSFLOAT lpgmf) {
  FIXME("(): stub !\n");

  return FALSE;
}

/************************************************************************
 * WGL_ARB_extensions_string extension
 */
const char* WINAPI wglGetExtensionsStringEXT()
{
  TRACE("()\n");
  return wgl_exts;
}

const char* WINAPI wglGetExtensionsStringARB(HDC hdc)
{
  TRACE("(%08x)\n", hdc);
  return wgl_exts;
}

/************************************************************************
 * WGL_ARB_pixel_format extension
 */
BOOL WINAPI wglGetPixelFormatAttribivARB(HDC hdc,
					int iPixelFormat,
					int iLayerPlane,
					UINT nAttributes,
					const int *piAttributes,
					int *piValues)
{
  FIXME("stub\n");
  return FALSE;
}

BOOL WINAPI wglGetPixelFormatAttribfvARB(HDC hdc,
					 int iPixelFormat,
					 int iLayerPlane,
					 UINT nAttributes,
					 const int *piAttributes,
					 FLOAT *pfValues)
{
  FIXME("stub\n");
  return FALSE;
}

BOOL WINAPI wglChoosePixelFormatARB(HDC hdc,
				    const int *piAttribIList,
				    const FLOAT *pfAttribFList,
				    UINT nMaxFormats,
				    int *piFormats,
				    UINT *nNumFormats)
{
  TRACE("(%08x,%p,%p,%d,%p,%p): semi-stub\n", hdc,
    piAttribIList, pfAttribFList, nMaxFormats, piFormats, nNumFormats);

  /* currently we don't really care about pixel formats.
   * Just return the current one. */
  *piFormats = GetPixelFormat(hdc);
  *nNumFormats = 1;
  return TRUE;
}

/************************************************************************
 * WGL_ARB_pbuffer extension
 *
 * Currently we're just naively piggybacking on top of the Direct3D HAL's
 * offscreen-rendering support, which uses pbuffers in our x11drv
 * implementation, so we'll just be using that. (Unfortunately the D3D HAL
 * can not yet be told to create double-buffered pbuffers, or any other
 * special attributes, so let's hope nobody really needs those.)
 */
HPBUFFERARB WINAPI wglCreatePbufferARB(HDC hDC,
				       int iPixelFormat,
				       int iWidth,
				       int iHeight,
				       const int *piAttribList)
{
  HBITMAP bmp;

  TRACE("(%08x,%d,%d,%d,%p)\n", hDC,
        iPixelFormat, iWidth, iHeight, piAttribList);

  /* Create a placeholder GDI bitmap object, which the D3D HAL
   * can use to find the size of the pbuffer it needs to create. */
  bmp = CreateBitmap(iWidth, iHeight,
                     GetDeviceCaps(hDC, PLANES),
                     GetDeviceCaps(hDC, BITSPIXEL),
                     NULL);

  TRACE("=> %08x\n", bmp);
  return bmp;
}

HDC WINAPI wglGetPbufferDCARB(HPBUFFERARB hPbuffer)
{
  HDC hDC;
  TRACE("(%08x)\n", hPbuffer);
  /* Create a DC for wglMakeCurrent. */
  hDC = CreateCompatibleDC(0);
  /* Selecting the bitmap into the DC will unfortunately
   * allocate a pixmap for it, which is somewhat wasteful.
   * But we can look into ways to avoid it later. */
  SelectObject(hDC, hPbuffer);
  /* init_context() can now find the bitmap in the memory DC,
   * and give it to the D3D HAL. Since the HAL knows it can't
   * render directly to a bitmap, it will create a pbuffer
   * which it will actually render to. (Normally the D3D HAL
   * will also try to synchronize the pbuffer and bitmap, but
   * that will only happen when the bitmap is a DIB section,
   * and this bitmap isn't, so that won't cause issues.) */
  TRACE("=> %08x\n", hDC);
  return hDC;
}

int WINAPI wglReleasePbufferDCARB(HPBUFFERARB hPbuffer,
				  HDC hDC)
{
  TRACE("(%08x,%08x)\n", hPbuffer, hDC);
  return DeleteDC(hDC);
}

BOOL WINAPI wglDestroyPbufferARB(HPBUFFERARB hPbuffer)
{
  TRACE("(%08x)\n", hPbuffer);
  return DeleteObject(hPbuffer);
}

BOOL WINAPI wglQueryPbufferARB(HPBUFFERARB hPbuffer,
			       int iAttribute,
			       int *piValue)
{
  BITMAP info;
  TRACE("(%08x,0x%x,%p)\n", hPbuffer, iAttribute, piValue);
  if (GetObjectA(hPbuffer, sizeof(info), &info) != sizeof(info)) {
    ERR("invalid bitmap %08x\n", hPbuffer);
    SetLastError(ERROR_INVALID_HANDLE);
    return FALSE;
  }
  switch (iAttribute) {
  case 0x2034: /* WGL_PBUFFER_WIDTH_ARB */
    *piValue = info.bmWidth;
    break;
  case 0x2035: /* WGL_PBUFFER_HEIGHT_ARB */
    *piValue = info.bmHeight;
    break;
  case 0x2036: /* WGL_PBUFFER_LOST_ARB */
    *piValue = FALSE;
    break;
  default:
    ERR("unknown attribute 0x%x\n", iAttribute);
    SetLastError(ERROR_INVALID_DATA);
    return FALSE;
  }
  return TRUE;
}

/************************************************************************
 * WGL_EXT_swap_control extension
 *  - this could be implemented using GLX_SGI_swap_control if necessary
 */
BOOL WINAPI wglSwapIntervalEXT(int interval)
{
  FIXME("(%d): stub!!\n", interval);

  return FALSE;
}

int WINAPI wglGetSwapIntervalEXT(void)
{
  FIXME("(): stub!\n");
  return 1;
}
/************************************************************************/

struct wgl_list_entry {
  const char *wgl_ext;
  const char *glx_ext;
  BOOL need_d3d_hal;
} wgl_list[] = {
  {"WGL_ARB_extensions_string", NULL, FALSE},
  {"WGL_ARB_pixel_format", NULL, TRUE},
  {"WGL_ARB_pbuffer", NULL, TRUE},
  {"WGL_EXT_extensions_string", NULL, FALSE},
  {"WGL_EXT_swap_control", "GLX_SGI_swap_control", FALSE},
  {NULL, NULL}
};

void init_wgl_ext(void)
{
  struct wgl_list_entry *current;
  const char *glx_ext;
  unsigned len;

  glx_ext = glXQueryExtensionsString(default_display, DefaultScreen(default_display));
  TRACE("GLX extensions: %s\n", glx_ext);
  len = 0;
  for (current = wgl_list; current->wgl_ext; current++) {
    if (current->glx_ext && !strstr(glx_ext, current->glx_ext)) continue;
    if (current->need_d3d_hal && !dd_gbl) continue;
    len += strlen(current->wgl_ext)+1;
  }
  wgl_exts = HeapAlloc(GetProcessHeap(), 0, len);
  len = 0;
  for (current = wgl_list; current->wgl_ext; current++) {
    if (current->glx_ext && !strstr(glx_ext, current->glx_ext)) continue;
    if (current->need_d3d_hal && !dd_gbl) continue;
    if (len) wgl_exts[len++] = ' ';
    len += strlen(strcpy(wgl_exts+len, current->wgl_ext));
  }
  TRACE("WGL extensions: %s\n", wgl_exts);
}

/* This is for brain-dead applications that use OpenGL functions before even
   creating a rendering context.... */
static void process_attach(void) {
  XWindowAttributes win_attr;
  Visual *rootVisual;
  int num;
  XVisualInfo template;
  XVisualInfo *vis = NULL;
  Window root;
  HDC hdc;

  context_array = NULL;

#ifdef USE_D3D_HAL
  __wine_get_hal(&dd_gbl, &dd_get_driver_info);
#endif
  if (dd_gbl) {
    D3DHAL_CONTEXTCREATEDATA data;

    dd_cbs = dd_gbl->lpDDCBtmp;
    d3d_hal_data = (LPVOID)dd_gbl->lpD3DGlobalDriverData;
    d3d_hal_cbs1 = (LPVOID)dd_gbl->lpD3DHALCallbacks;
    d3d_hal_cbs2 = (LPVOID)dd_gbl->lpD3DHALCallbacks2;

    TRACE("DirectDraw HAL context management enabled\n");

    /* create default context */
    data.u1.lpDDLcl = NULL;
    data.u2.lpDDSLcl = NULL;
    data.u3.lpDDSZLcl = NULL;
    data.u4.dwPID = (DWORD)-1;
    data.dwhContext = 0;
    d3d_hal_cbs1->ContextCreate(&data);
    if (data.ddrval != S_OK) {
      ERR("Could not create default context\n");
      return;
    }
    dwhDefContext = data.dwhContext;
    ENTER_GL();
    default_cx = glXGetCurrentContext();
    default_display = glXGetCurrentDisplay();
    init_wgl_ext();
    LEAVE_GL();
    return;
  }
  else {
    dd_cbs = NULL;
    d3d_hal_data = NULL;
    d3d_hal_cbs1 = NULL;
    d3d_hal_cbs2 = NULL;

    TRACE("DirectDraw HAL context management disabled\n");
  }

  root = (Window)GetPropA( GetDesktopWindow(), "__wine_x11_whole_window" );
  if (!root)
  {
    ERR("X11DRV not loaded. Cannot create default context.\n");
    return;
  }

  hdc = GetDC(0);
  default_display = get_display(hdc);
  ReleaseDC(0, hdc);

  if (!default_display)
  {
      ERR("X11DRV not loaded. Cannot get default display.\n");
      return;
  }

  ENTER_GL();

  /* Try to get the visual from the Root Window.  We can't use the standard (presumably
     double buffered) X11DRV visual with the Root Window, since we don't know if the Root
     Window was created using the standard X11DRV visual, and glXMakeCurrent can't deal
     with mismatched visuals.  Note that the Root Window visual may not be double
     buffered, so apps actually attempting to render this way may flicker */
  if (XGetWindowAttributes( default_display, root, &win_attr ))
  {
    rootVisual = win_attr.visual;
  }
  else
  {
    /* Get the default visual, since we can't seem to get the attributes from the
       Root Window.  Let's hope that the Root Window Visual matches the DefaultVisual */
    rootVisual = DefaultVisual( default_display, DefaultScreen(default_display) );
  }

  template.visualid = XVisualIDFromVisual(rootVisual);
  vis = XGetVisualInfo(default_display, VisualIDMask, &template, &num);
  if (vis != NULL)        default_cx = glXCreateContext(default_display, vis, 0, GL_TRUE);
  if (default_cx != NULL) glXMakeCurrent(default_display, root, default_cx);
  XFree(vis);

  init_wgl_ext();

  LEAVE_GL();

  if (default_cx == NULL) {
    ERR("Could not create default context.\n");
  }
}

static void process_detach(void)
{
  if( wgl_exts ) HeapFree( GetProcessHeap(), 0, wgl_exts );

  if (dwhDefContext) {
    D3DHAL_CONTEXTDESTROYDATA data;
    data.dwhContext = dwhDefContext;
    d3d_hal_cbs1->ContextDestroy(&data);
    dwhDefContext = 0;
    return;
  }

  glXDestroyContext(default_display, default_cx);
}

/***********************************************************************
 *           OpenGL initialisation routine
 */
BOOL WINAPI OpenGL32_Init( HINSTANCE hinst, DWORD reason, LPVOID reserved )
{
  switch(reason) {
  case DLL_PROCESS_ATTACH:
    process_attach();
    break;
  case DLL_PROCESS_DETACH:
    process_detach();
    break;
  }
  return TRUE;
}
