/**************************************************************************\
 *
 *  This file is part of the Coin 3D visualization library.
 *  Copyright (C) 1998-2006 by Systems in Motion.  All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  ("GPL") version 2 as published by the Free Software Foundation.
 *  See the file LICENSE.GPL at the root directory of this source
 *  distribution for additional information about the GNU GPL.
 *
 *  For using Coin with software that can not be combined with the GNU
 *  GPL, and for taking advantage of the additional benefits of our
 *  support services, please contact Systems in Motion about acquiring
 *  a Coin Professional Edition License.
 *
 *  See http://www.coin3d.org/ for more information.
 *
 *  Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
 *  http://www.sim.no/  sales@sim.no  coin-support@coin3d.org
 *
\**************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#include <stdlib.h>
#include <assert.h>

#include <Inventor/C/glue/gl.h>
#include <Inventor/C/glue/glp.h>
#include <Inventor/C/errors/debugerror.h>
#include <Inventor/C/glue/gl_wgl.h>
#include <Inventor/C/tidbits.h>
#include <Inventor/C/glue/dl.h>
#include <Inventor/C/glue/dlp.h>

#ifdef HAVE_WIN32_API
/* Conditional inclusion, as the functions in win32api.h will not be
   implemented unless the Win32 API is available. */
#include <Inventor/C/glue/win32api.h>
#endif /* HAVE_WIN32_API */

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

#ifndef HAVE_WGL

/* Dummy implementations, for when WGL is not available: */

void * coin_wgl_getprocaddress(const char * fname) { return NULL; }

void * wglglue_context_create_offscreen(unsigned int width, unsigned int height) { assert(FALSE); return NULL; }
SbBool wglglue_context_make_current(void * ctx) { assert(FALSE); return FALSE; }
void wglglue_context_reinstate_previous(void * ctx) { assert(FALSE); }
void wglglue_context_destruct(void * ctx) { assert(FALSE); }

void wglglue_context_bind_pbuffer(void * ctx) { assert(FALSE); }
void wglglue_context_release_pbuffer(void * ctx) { assert(FALSE); }
SbBool wglglue_context_pbuffer_is_bound(void * ctx) { assert(FALSE); return FALSE; }
SbBool wglglue_context_can_render_to_texture(void * ctx) { assert(FALSE); return FALSE; }

SbBool wglglue_context_pbuffer_max(void * ctx, unsigned int * lims) { assert(FALSE); return FALSE; }

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

#else /* HAVE_WGL */

struct wglglue_contextdata;
static SbBool (* wglglue_context_create)(struct wglglue_contextdata * context, SbBool warnonerrors) = NULL;

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

/* Declared in the same manner as in the OpenGL ARB document for the
   pbuffer extension, but with a different name to avoid clashes upon
   static binding.

   Note that I couldn't find any documentation on the DECLARE_HANDLE
   thinga-majingy, so I would appreciate it if someone could confirm
   that this is correct.

   -mortene.
*/
DECLARE_HANDLE(WGLGLUE_HPBUFFER);

/* The following are from either the WGL_ARB_pbuffer or the
   WGL_EXT_pbuffer extensions: */

typedef WGLGLUE_HPBUFFER (WINAPI * COIN_PFNWGLCREATEPBUFFERPROC)(HDC hDC,
                                                               int iPixelFormat,
                                                               int iWidth,
                                                               int iHeight,
                                                               const int * piAttribList);
typedef HDC (WINAPI * COIN_PFNWGLGETPBUFFERDCPROC)(WGLGLUE_HPBUFFER hPbuffer);
typedef int (WINAPI * COIN_PFNWGLRELEASEPBUFFERDCPROC)(WGLGLUE_HPBUFFER hPbuffer,
                                                          HDC hDC);
typedef BOOL (WINAPI * COIN_PFNWGLDESTROYPBUFFERPROC)(WGLGLUE_HPBUFFER hPbuffer);
typedef BOOL (WINAPI * COIN_PFNWGLQUERYPBUFFERPROC)(WGLGLUE_HPBUFFER hPbuffer,
                                                       int iAttribute,
                                                       int * piValue);

typedef BOOL (WINAPI * COIN_PFNWGLBINDTEXIMAGEARBPROC)(WGLGLUE_HPBUFFER hPbuffer, int iBuffer);
typedef BOOL (WINAPI * COIN_PFNWGLRELEASETEXIMAGEARBPROC)(WGLGLUE_HPBUFFER hPbuffer, int iBuffer);

static COIN_PFNWGLCREATEPBUFFERPROC wglglue_wglCreatePbuffer = NULL;
static COIN_PFNWGLGETPBUFFERDCPROC wglglue_wglGetPbufferDC = NULL;
static COIN_PFNWGLRELEASEPBUFFERDCPROC wglglue_wglReleasePbufferDC = NULL;
static COIN_PFNWGLDESTROYPBUFFERPROC wglglue_wglDestroyPbuffer = NULL;
static COIN_PFNWGLQUERYPBUFFERPROC wglglue_wglQueryPbuffer = NULL;
static COIN_PFNWGLBINDTEXIMAGEARBPROC wglglue_wglBindTexImageARB = NULL;
static COIN_PFNWGLBINDTEXIMAGEARBPROC wglglue_wglReleaseTexImageARB = NULL;


/* The following is from either the WGL_ARB_pixel_format or the
   WGL_EXT_pixel_format extensions: */

typedef BOOL (WINAPI * COIN_PFNWGLCHOOSEPIXELFORMATPROC)(HDC hdc,
                                                         const int * piAttribIList,
                                                         const FLOAT * pfAttribFList,
                                                         UINT nMaxFormats,
                                                         int * piFormats,
                                                         UINT * nNumFormats);

typedef BOOL (WINAPI * COIN_PFNWGLGETPIXELFORMATATTRIBIVPROC)(HDC hdc,
                                                              int iPixelFormat,
                                                              int iLayerPlane,
                                                              UINT nAttribautes,
                                                              const int * piAttributes,
                                                              int * piValues);

static COIN_PFNWGLCHOOSEPIXELFORMATPROC wglglue_wglChoosePixelFormat = NULL;
static COIN_PFNWGLGETPIXELFORMATATTRIBIVPROC wglglue_wglGetPixelFormatAttribiv = NULL;

/* The function for finding extension strings is itself an extension
   string. */

typedef const char * (WINAPI * COIN_PFNWGLGETEXTENSIONSSTRING)(HDC hDC);

static COIN_PFNWGLGETEXTENSIONSSTRING wglglue_wglGetExtensionsString = NULL;

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

#ifdef HAVE_DYNAMIC_LINKING
#define PROC(_func_) cc_glglue_getprocaddress(SO__QUOTE(_func_))

/* The OpenGL library's WGL part which we dynamically pick up symbols
   from /could/ have these defined. For the code below which tries to
   dynamically resolve the methods, we will assume that they are
   defined. By doing this little "trick", can we use the same code
   below for resolving stuff dynamically as we need anyway to resolve
   in a static manner. */

#define WGL_ARB_pixel_format 1
#define WGL_EXT_pixel_format 1

#define WGL_ARB_pbuffer 1
#define WGL_EXT_pbuffer 1

#define WGL_ARB_extensions_string 1
#define WGL_EXT_extensions_string 1

#define WGL_ARB_render_texture 1
#else /* static binding */

#define PROC(_func_) (&_func_)

#endif /* static binding */

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

static cc_libhandle glhnd = NULL;
static SbBool tried_fetching_handle = FALSE;
static SbBool attemptedextresolved = FALSE;

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

void *
coin_wgl_getprocaddress(const char * fname)
{
  void * ptr = wglGetProcAddress(fname);

  /* wglGetProcAddress() seems to only be able to fetch
     function-addresses for *extension* functions, not "proper" OpenGL
     (1.1+) functions. */

  if (ptr == NULL) {
    if (!tried_fetching_handle) {
      glhnd = cc_dl_handle_with_gl_symbols();
      tried_fetching_handle = TRUE;

      if (!glhnd && coin_glglue_debug()) {
        cc_debugerror_postwarning("coin_wgl_getprocaddress",
                                  "couldn't get hold of any workable module "
                                  "handle for picking up OpenGL symbols");
      }
    }

    if (glhnd) {
      ptr = cc_dl_sym(glhnd, fname);

      if (ptr && coin_glglue_debug()) {
        cc_debugerror_postinfo("coin_wgl_getprocaddress",
                               "wglGetProcAddress() missed \"%s\", "
                               "but found with cc_dl_sym()", fname);
      }
    }
  }

  return ptr;
}

/*** WGL offscreen contexts ***********************************************/

struct wglglue_contextdata {
  unsigned int width, height;

  HDC memorydc;
  HWND pbufferwnd;
  SbBool didcreatememorydc;
  SbBool shouldreleasememorydc;
  HBITMAP bitmap, oldbitmap;
  HGLRC wglcontext;

  HGLRC storedcontext;
  HDC storeddc;

  WGLGLUE_HPBUFFER hpbuffer;
  SbBool noappglcontextavail;

  SbBool supports_render_to_texture;
  SbBool pbufferisbound;

  int pixelformat;
};

static SbBool
wglglue_pbuffer_symbols_resolved(void)
{
  return (wglglue_wglCreatePbuffer && wglglue_wglGetPbufferDC &&
          wglglue_wglReleasePbufferDC && wglglue_wglDestroyPbuffer &&
          wglglue_wglQueryPbuffer);
}

static SbBool
wglglue_ext_supported(struct wglglue_contextdata * context, const char * reqext)
{
  /*
   * wgl extensions are not necessarily listed in the string returned
   * by glGetString(GL_EXTENSIONS), see e.g.
   *
   *   http://www.gamedev.net/reference/articles/article1929.asp
   *
   * ..so we try to get hold of wglGetExtensionsString[ARB|EXT]() and
   * use that aswell.
   */
  if (!attemptedextresolved) {
    attemptedextresolved = TRUE;
#ifdef WGL_ARB_extensions_string
    wglglue_wglGetExtensionsString = (COIN_PFNWGLGETEXTENSIONSSTRING)PROC(wglGetExtensionsStringARB);
#endif /* WGL_ARB_extensions_string */
#ifdef WGL_EXT_extensions_string
    if (!wglglue_wglGetExtensionsString) {
      wglglue_wglGetExtensionsString = (COIN_PFNWGLGETEXTENSIONSSTRING)PROC(wglGetExtensionsStringEXT);
    }
#endif /* WGL_EXT_extensions_string */
  }

  if (wglglue_wglGetExtensionsString) {
    const char * wglext = wglglue_wglGetExtensionsString(context->memorydc);
    if (coin_glglue_extension_available(wglext, reqext)) { return TRUE; }
  }

  if (coin_glglue_extension_available(glGetString(GL_EXTENSIONS), reqext)) { return TRUE; }

  return FALSE;
}

static SbBool
wglglue_resolve_symbols(struct wglglue_contextdata * context)
{
  /* Short circuit out if symbols have already been resolved. */
  if (wglglue_pbuffer_symbols_resolved()) { return TRUE; }

  /* We need a(ny) current context to resolve symbols. */
  if (!wglglue_context_make_current(context)) { return FALSE; }

  /* Attempt to resolve the symbols: */


  /* Check EXT before ARB, to let the latter override the former if
     both are present, as ARB should always be more recent than
     EXT. */

#ifdef WGL_EXT_pixel_format
  if (wglglue_ext_supported(context, "WGL_EXT_pixel_format")) {
    wglglue_wglChoosePixelFormat = (COIN_PFNWGLCHOOSEPIXELFORMATPROC)PROC(wglChoosePixelFormatEXT);
    wglglue_wglGetPixelFormatAttribiv = (COIN_PFNWGLGETPIXELFORMATATTRIBIVPROC)PROC(wglGetPixelFormatAttribivEXT);
  }
#endif /* WGL_EXT_pixel_format */

#ifdef WGL_ARB_pixel_format
  if (wglglue_ext_supported(context, "WGL_ARB_pixel_format")) {
    wglglue_wglChoosePixelFormat = (COIN_PFNWGLCHOOSEPIXELFORMATPROC)PROC(wglChoosePixelFormatARB);
    wglglue_wglGetPixelFormatAttribiv = (COIN_PFNWGLGETPIXELFORMATATTRIBIVPROC)PROC(wglGetPixelFormatAttribivARB);
  }
#endif /* WGL_ARB_pixel_format */


  /* Now check ARB before EXT, as EXT-check will be blocked if
     ARB-versions of symbols are found. */

#ifdef WGL_ARB_pbuffer
  if (wglglue_wglChoosePixelFormat && /* <- WGL_*_pbuffer depends on WGL_*_pixel_format */
      wglglue_ext_supported(context, "WGL_ARB_pbuffer")) {
    wglglue_wglCreatePbuffer = (COIN_PFNWGLCREATEPBUFFERPROC)PROC(wglCreatePbufferARB);
    wglglue_wglGetPbufferDC = (COIN_PFNWGLGETPBUFFERDCPROC)PROC(wglGetPbufferDCARB);
    wglglue_wglReleasePbufferDC = (COIN_PFNWGLRELEASEPBUFFERDCPROC)PROC(wglReleasePbufferDCARB);
    wglglue_wglDestroyPbuffer = (COIN_PFNWGLDESTROYPBUFFERPROC)PROC(wglDestroyPbufferARB);
    wglglue_wglQueryPbuffer = (COIN_PFNWGLQUERYPBUFFERPROC)PROC(wglQueryPbufferARB);
  }
#endif /* WGL_ARB_pbuffer */

#ifdef WGL_EXT_pbuffer
  if (!wglglue_pbuffer_symbols_resolved() &&
      wglglue_wglChoosePixelFormat && /* <- WGL_*_pbuffer depends on WGL_*_pixel_format */
      wglglue_ext_supported(context, "WGL_EXT_pbuffer")) {
    wglglue_wglCreatePbuffer = (COIN_PFNWGLCREATEPBUFFERPROC)PROC(wglCreatePbufferEXT);
    wglglue_wglGetPbufferDC = (COIN_PFNWGLGETPBUFFERDCPROC)PROC(wglGetPbufferDCEXT);
    wglglue_wglReleasePbufferDC = (COIN_PFNWGLRELEASEPBUFFERDCPROC)PROC(wglReleasePbufferDCEXT);
    wglglue_wglDestroyPbuffer = (COIN_PFNWGLDESTROYPBUFFERPROC)PROC(wglDestroyPbufferEXT);
    wglglue_wglQueryPbuffer = (COIN_PFNWGLQUERYPBUFFERPROC)PROC(wglQueryPbufferEXT);
  }
#endif /* WGL_EXT_pbuffer */

#ifdef WGL_ARB_render_texture
  if (wglglue_ext_supported(context, "WGL_ARB_render_texture")) {
    wglglue_wglBindTexImageARB = (COIN_PFNWGLBINDTEXIMAGEARBPROC) PROC(wglBindTexImageARB);
    wglglue_wglReleaseTexImageARB = (COIN_PFNWGLBINDTEXIMAGEARBPROC) PROC(wglReleaseTexImageARB);
  }
#endif /* WGL_ARB_render_texture */

  wglglue_context_reinstate_previous(context);

  return wglglue_pbuffer_symbols_resolved();
}

static struct wglglue_contextdata *
wglglue_contextdata_init(unsigned int width, unsigned int height)
{
  struct wglglue_contextdata * context;

  context = (struct wglglue_contextdata *)malloc(sizeof(struct wglglue_contextdata));

  context->width = width;
  context->height = height;
  context->memorydc = NULL;
  context->pbufferwnd = NULL;
  context->didcreatememorydc = FALSE;
  context->shouldreleasememorydc = FALSE;
  context->bitmap = NULL;
  context->hpbuffer = NULL;
  context->oldbitmap = NULL;
  context->wglcontext = NULL;
  context->storedcontext = NULL;
  context->storeddc = NULL;
  context->noappglcontextavail = FALSE;
  context->supports_render_to_texture = FALSE;
  context->pbufferisbound = FALSE;
  context->pixelformat = 0;

  return context;
}

static void
wglglue_contextdata_cleanup(struct wglglue_contextdata * ctx)
{
  if (ctx == NULL) { return; }

  /* FIXME: the error handling below can and should be simplified, by
     implementing and using excpetion catching wrappers from
     glue/win32api. 20031124 mortene. */

  if (ctx->wglcontext && ctx->noappglcontextavail) {
    const BOOL r = wglDeleteContext(ctx->wglcontext);
    if (!r) {
      cc_win32_print_error("wglglue_contextdata_cleanup",
                           "wglDeleteContext", GetLastError());
    }
  }
  if (ctx->oldbitmap) {
    const HGDIOBJ o = SelectObject(ctx->memorydc, ctx->oldbitmap);
    if (!o) {
      cc_win32_print_error("wglglue_contextdata_cleanup",
                           "SelectObject", GetLastError());
    }
  }
  if (ctx->bitmap) {
    const BOOL r = DeleteObject(ctx->bitmap);
    if (!r) {
      cc_win32_print_error("wglglue_contextdata_cleanup",
                           "DeleteObject", GetLastError());
    }
  }
  if (ctx->hpbuffer) {
    {
      const int r = wglglue_wglReleasePbufferDC(ctx->hpbuffer, ctx->memorydc);
      if (!r) {
        cc_win32_print_error("wglglue_contextdata_cleanup",
                             "wglReleasePbufferDC", GetLastError());
      }
    }
    {
      const BOOL r = wglglue_wglDestroyPbuffer(ctx->hpbuffer);
      if (!r) {
        cc_win32_print_error("wglglue_contextdata_cleanup",
                             "wglDestroyPbuffer", GetLastError());
      }
    }
  }
  if (ctx->memorydc) {
    if (ctx->didcreatememorydc) {
      const BOOL r = DeleteDC(ctx->memorydc);
      if (!r) {
        cc_win32_print_error("wglglue_contextdata_cleanup",
                             "DeleteDC", GetLastError());
      }
    }
    else if (ctx->shouldreleasememorydc && ctx->pbufferwnd) {
      int ok = ReleaseDC(ctx->pbufferwnd, ctx->memorydc);
      if (!ok) {
        cc_win32_print_error("wglglue_contextdata_cleanup",
                             "ReleaseDC", GetLastError());
      }
    }
  }
  if (ctx->pbufferwnd) {
    BOOL r = DestroyWindow(ctx->pbufferwnd);
    if (!r) {
      cc_win32_print_error("wglglue_contextdata_cleanup",
                           "DestroyWindow", GetLastError());
    }
  }

  free(ctx);
}

static SbBool
wglglue_context_create_context(struct wglglue_contextdata * ctx, DWORD bitWin)
{
  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;
  int pixelformat;

  PIXELFORMATDESCRIPTOR pfd = {
    sizeof(PIXELFORMATDESCRIPTOR),   /* size of this pfd */
    1,                     /* version number */
    bitWin |               /* support bitmap or window */
    PFD_SUPPORT_OPENGL,    /* support OpenGL */
    PFD_TYPE_RGBA,         /* RGBA type */
    24,                    /* 24-bit color depth */
    0, 0, 0, 0, 0, 0,      /* color bits ignored */
    8,                     /* 8 bit alpha channel */
    0,                     /* shift bit ignored */
    0,                     /* no accumulation buffer */
    0, 0, 0, 0,            /* accum bits ignored */
    32,                    /* 32-bit z-buffer */
    1,                     /* minimum size stencil buffer */
    0,                     /* no auxiliary buffer */
    PFD_MAIN_PLANE,        /* main layer */
    0,                     /* reserved */
    0, 0, 0                /* layer masks ignored */
  };

  /* FIXME: the following is a hack to get around a problem which
     really demands more effort to be solved properly.

     The problem is that there is no way in the API of the
     SoOffscreenRenderer class to specify what particular attributes
     to request. This most often manifests itself as a problem for app
     programmers in that they have made some kind of extension node
     which uses the OpenGL stencil buffer. If no stencil buffer
     happens to be part of the GL context format for the offscreen
     renderer, these will not work properly. At the same time, we
     don't want to default to requesting a stencil buffer, as that
     takes a non-trivial amount of extra memory resources on the gfx
     card.

     So until we have implemented the proper solution for making it
     possible to pass in a detailed specification of which attributes
     to request from offscreen GL contexts, we provide this temporary
     work-around: the app programmer can set an envvar with a value
     specifying the number of stencil buffer bits he/she wants.

     20060223 mortene.
  */
  const int v = coin_glglue_stencil_bits_hack();
  if (v != -1) { pfd.cStencilBits = (BYTE)v; }


  /* get the best available match of pixel format for the device
     context: */

  /* FIXME: instead of ChoosePixelFormat(), we should use the code
     from SoWin which runs through the available formats and weights
     them according to our own rules. Simplifies debugging immensely
     when something goes wrong, and provides ways to override parts of
     the selection process. 20040608 mortene. */

  pixelformat = ChoosePixelFormat(context->memorydc, &pfd);
  if (pixelformat == 0) {
    DWORD dwError = GetLastError();
    cc_debugerror_postwarning("wglglue_context_create_context",
                              "ChoosePixelFormat() failed with "
                              "error code %d.", dwError);
    return FALSE;
  }

  /* make that the pixel format of the device context: */

  /* I've seen a driver (3Dlabs GLINT R3 PT, 1.1.28) where
     SetPixelFormat() failed but didn't set GetLastError(), resulting
     in a nonsensical error message, so we reset this
     first. -mortene. */
  SetLastError(0);

  if (!SetPixelFormat(context->memorydc, pixelformat, &pfd)) {
    DWORD dwError = GetLastError();
    cc_string str;

    cc_string_construct(&str);
    cc_string_sprintf(&str, "SetPixelFormat(%p, %d, ...)", context->memorydc, pixelformat);
    cc_win32_print_error("wglglue_context_create_context", cc_string_get_text(&str), dwError);
    cc_string_clean(&str);

    /* We have had an external error report about this, plus I've been
       able to reproduce it locally on ASK (Win NT 4, 3Dlabs 1.1.28):
       just set the display to "Truecolor" (instead of "256 colors" or
       "65536 colors" in the system display settings panel) and the
       SetPixelFormat() above will fail. Changing the display settings
       to a lower color resolution fixes it. (Using a lower screen
       resolution makes no difference, so it doesn't seem to be
       related to lack of memory resources.)

       I tried to make less demanding requirements for the pixel
       format in the above PIXELFORMATDESCRIPTOR, but that didn't help
       either.

       So, this seems very much like a system / driver bug. 
    */

    cc_debugerror_post("wglglue_context_create_context",
                       "This is most likely a bug with your system. "
                       "Try changing display settings (to e.g. 16-bit, "
                       "24-bit or 32-bit truecolor display) and re-run.");

    return FALSE;
  }

  context->pixelformat = pixelformat;

  context->wglcontext = wglCreateContext(context->memorydc);
  if (context->wglcontext == NULL) {
    DWORD dwError = GetLastError();
    cc_win32_print_error("wglglue_context_create_context",
                         "wglCreateContext()", dwError);
    return FALSE;
  }

  return TRUE;
}

static SbBool
wglglue_context_create_software(struct wglglue_contextdata * ctx, SbBool warnonerrors)
{
  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;

  if (coin_glglue_debug()) {
    cc_debugerror_postinfo("wglglue_context_create_software",
                           "creating software buffer");
  }

  context->memorydc = CreateCompatibleDC(NULL);
  context->didcreatememorydc = TRUE;
  context->shouldreleasememorydc = FALSE;
  if (context->memorydc == NULL) {
    if (warnonerrors || coin_glglue_debug()) {
      DWORD dwError = GetLastError();
      cc_debugerror_postwarning("wglglue_context_create_software",
                                "CreateCompatibleDC(NULL) failed with "
                                "error code %d.", dwError);
    }
    return FALSE;
  }

  /* make a bitmap to draw to */
  {
    BITMAPINFO bmi;
    void * pvbits;

    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = context->width;
    bmi.bmiHeader.biHeight = context->height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 24;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = 0;
    bmi.bmiHeader.biXPelsPerMeter = 0;
    bmi.bmiHeader.biYPelsPerMeter = 0;
    bmi.bmiHeader.biClrUsed  = 0;
    bmi.bmiHeader.biClrImportant = 0;
    bmi.bmiColors[0].rgbBlue = 0;
    bmi.bmiColors[0].rgbGreen = 0;
    bmi.bmiColors[0].rgbRed = 0;
    bmi.bmiColors[0].rgbReserved = 0;

    context->bitmap = CreateDIBSection(context->memorydc, &bmi, DIB_RGB_COLORS,
                                      &pvbits, NULL, 0);
    if (context->bitmap == NULL) {
      if (warnonerrors || coin_glglue_debug()) {
        DWORD dwError = GetLastError();
        cc_debugerror_postwarning("wglglue_context_create_software",
                                  "CreateDIBSection() failed with error "
                                  "code %d.", dwError);
      }
      return FALSE;
    }
  }

  context->oldbitmap = (HBITMAP)
    SelectObject(context->memorydc, context->bitmap);
  if (context->oldbitmap == NULL) {
    if (warnonerrors || coin_glglue_debug()) {
      DWORD dwError = GetLastError();
      cc_debugerror_postwarning("wglglue_context_create_software",
                                "SelectObject() failed with error code %d.",
                                dwError);
    }
    return FALSE;
  }

  if (!(wglglue_context_create_context(context, PFD_DRAW_TO_BITMAP))) {
    return FALSE;
  }

  if (coin_glglue_debug()) {
    cc_debugerror_postinfo("wglglue_context_create_software",
                           "success creating software buffer, HGLRC==%p",
                           context->wglcontext);
  }

  return TRUE;
}

static SbBool
wglglue_context_create_pbuffer(struct wglglue_contextdata * ctx, SbBool warnonerrors)
{
  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;

  if (coin_glglue_debug()) {
    cc_debugerror_postinfo("wglglue_context_create_pbuffer", "creating pbuffer");
  }

  if ((context->memorydc = wglGetCurrentDC())) {
    context->shouldreleasememorydc = FALSE;
    context->didcreatememorydc = FALSE;
    context->wglcontext = wglGetCurrentContext();
  }
  else { context->noappglcontextavail = TRUE; }

  if (context->noappglcontextavail) {
    /* FIXME: This should be reset in wglglue_cleanup() once we
       also properly unregister there... 20060207 kyrah */
    static int didregister = 0;
    if (!didregister) {
      WNDCLASS wc;
      didregister = 1;

      wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
      wc.lpfnWndProc    = DefWindowProc;
      wc.cbClsExtra     = 0;
      wc.cbWndExtra     = 0;
      wc.hInstance      = GetModuleHandle(NULL);
      wc.hIcon          = NULL;
      wc.hCursor        = NULL;
      wc.hbrBackground  = NULL;
      wc.lpszMenuName   = NULL;
      wc.lpszClassName  = "coin_gl_wgl";

      if (!RegisterClass(&wc)) {
        DWORD dwError = GetLastError();
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "RegisterClass(&wc) failed with "
                                  "error code %d.", dwError);
        return FALSE;
      }
      /* FIXME: unregister at app exit? pederb, 2003-12-15 */
    }

    {
      HWND hWnd;
      HINSTANCE hInstance = GetModuleHandle(NULL);

      if (!(hWnd = CreateWindow(
                     "coin_gl_wgl",   /* class name */
                     "coin_gl_wgl",   /* window title */
                     0,               /* selected window style */
                     0, 0,            /* window position */
                     context->width,  /* calculate adjusted window width */
                     context->height, /* calculate adjusted window height */
                     NULL,            /* no parent window */
                     NULL,            /* no menu */
                     hInstance,       /* Instance */
                     NULL)))          /* don't pass anything to WM_CREATE */
      {
        DWORD dwError = GetLastError();
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "CreateWindow(...) failed with "
                                  "error code %d.", dwError);
        return FALSE;
      }

      context->pbufferwnd = hWnd;
      context->memorydc = GetDC(hWnd);
      context->shouldreleasememorydc = TRUE;
      context->didcreatememorydc = FALSE;
      if (context->memorydc == NULL) {
        DWORD dwError = GetLastError();
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "GetDC(hWnd) failed with "
                                  "error code %d.", dwError);
        return FALSE;
      }
    }

    if (!(wglglue_context_create_context(context, PFD_DRAW_TO_WINDOW))) {
      return FALSE;
    }
  }

  {
    const SbBool pbuffer = wglglue_resolve_symbols(context);

    if (coin_glglue_debug()) {
      cc_debugerror_postinfo("wglglue_context_create_pbuffer",
                             "PBuffer offscreen rendering is %ssupported "
                             "by the OpenGL driver", pbuffer ? "" : "NOT ");
    }

    if (!pbuffer) {
      return FALSE;
    }
  }

  {
    GLint pixformat;
    unsigned int numFormats;
    const float fAttribList[] = { 0 };

    int nontex_attrs[] = {
      WGL_STENCIL_BITS_ARB, 1, /* FIXME: must be first, since we may
                                  want to modify it later, and need to
                                  know where it is. This is a
                                  wart. 20060307 mortene. */
      WGL_DRAW_TO_PBUFFER_ARB, TRUE,
      WGL_COLOR_BITS_ARB, 32,
      WGL_ALPHA_BITS_ARB, 8,
      WGL_DEPTH_BITS_ARB, 24,
      0
    };
    int tex_attrs[] = {
      WGL_STENCIL_BITS_ARB, 1, /* FIXME: must be first, since we may
                                  want to modify it later, and need to
                                  know where it is. This is a
                                  wart. 20060307 mortene. */
      WGL_DRAW_TO_PBUFFER_ARB, TRUE,
      WGL_BIND_TO_TEXTURE_RGBA_ARB, TRUE,
      WGL_COLOR_BITS_ARB, 32,
      WGL_ALPHA_BITS_ARB, 8,
      WGL_DEPTH_BITS_ARB, 24,
      0
    };
    const int * attrs[] = { nontex_attrs, tex_attrs };

    const int nontex_pbufferflags[] = { 0 };
    const int tex_pbufferflags[] = {
      WGL_TEXTURE_FORMAT_ARB, WGL_TEXTURE_RGBA_ARB,
      WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_2D_ARB,
      0
    };
    const int * pbufferflags[] = { nontex_pbufferflags, tex_pbufferflags };

    unsigned int try;

    {
      /* FIXME: the following is a hack to get around a problem which
         really demands more effort to be solved properly. See further
         up in this source file, in the function to create a software
         context, for a more elaborate explanation. 20060307 mortene.
      */
      const int v = coin_glglue_stencil_bits_hack();
      if (v != -1) {
        assert(nontex_attrs[0] == WGL_STENCIL_BITS_ARB);
        nontex_attrs[1] = v;
        assert(tex_attrs[0] == WGL_STENCIL_BITS_ARB);
        tex_attrs[1] = v;
      }
    }

    /* iterate from end of arrays, which contains "best" option */
    try = (sizeof(attrs) / sizeof(attrs[0]));
    /* if render-to-texture extension not supported, don't attempt to
       set up a pbuffer with those capabilities (could in theory cause
       nasty WGL errors): */
    if (wglglue_wglBindTexImageARB == NULL) { try--; }

    while (try > 0) {
      try--;

      /* choose pixel format */
      if (!wglglue_wglChoosePixelFormat(context->memorydc, attrs[try],
                                        fAttribList, 1, &pixformat,
                                        &numFormats)) {
        if (warnonerrors || coin_glglue_debug()) {
          cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                    "wglChoosePixelFormat() failed, try %u",
                                    try);
        }
        continue;
      }

      /* create the pbuffer */
      context->hpbuffer = wglglue_wglCreatePbuffer(context->memorydc,
                                                   pixformat,
                                                   context->width,
                                                   context->height,
                                                   pbufferflags[try]);
      if (!context->hpbuffer) {
        if (warnonerrors || coin_glglue_debug()) {
          cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                    "wglCreatePbuffer(HDC, PixelFormat==%d, "
                                    "Width==%d, Height==%d, AttribList==%p) "
                                    "failed, try %u",
                                    pixformat,
                                    context->width, context->height,
                                    pbufferflags[try], try);
        }
        continue;
      }

      /* success, set capability flag, and break loop */
      if (coin_glglue_debug()) {
        cc_debugerror_postinfo("wglglue_context_create_pbuffer",
                               "wglCreatePbuffer() success, try %u", try);
      }
      context->supports_render_to_texture =
        (pbufferflags[try] == tex_pbufferflags);
      break;
    }

    /* if no way to construct a pbuffer was found: */
    if (!context->hpbuffer) { return FALSE; }

    context->pixelformat = pixformat;

    /* delete/release device context and window in case we created it
       ourselves */
    if (context->memorydc) {
      if (context->didcreatememorydc) {
        BOOL r = DeleteDC(context->memorydc);
        if (!r) {
          cc_win32_print_error("wglglue_context_create_pbuffer",
                               "DeleteDC", GetLastError());
        }
      }
      else if (context->shouldreleasememorydc && context->pbufferwnd) {
        BOOL r = ReleaseDC(context->pbufferwnd, context->memorydc);
        if (!r) {
          cc_win32_print_error("wglglue_context_create_pbuffer",
                               "ReleaseDC", GetLastError());
        }
      }
    }
    if (context->pbufferwnd) {
      BOOL r = DestroyWindow(context->pbufferwnd);
      if (!r) {
        cc_win32_print_error("wglglue_context_create_pbuffer",
                             "DestroyWindow", GetLastError());
      }
      context->pbufferwnd = NULL;
    }

    context->memorydc = wglglue_wglGetPbufferDC(context->hpbuffer);
    context->didcreatememorydc = FALSE;
    context->shouldreleasememorydc = FALSE;
    if (!context->memorydc) {
      if (warnonerrors || coin_glglue_debug()) {
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "Couldn't create pbuffer's device context.");
      }
      return FALSE;
    }

    /* delete wgl context in case we created it ourselves */
    if (context->noappglcontextavail) {
      BOOL r = wglDeleteContext(context->wglcontext);
      if (!r) {
        if (warnonerrors || coin_glglue_debug()) {
          cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                    "Couldn't create pbuffer's device context.");
        }
      }
    }
    context->wglcontext = wglCreateContext(context->memorydc);
    if (!context->wglcontext) {
      if (warnonerrors || coin_glglue_debug()) {
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "Couldn't create rendering context for the pbuffer.");
      }
      return FALSE;
    }

    /* set and output the actual pBuffer dimensions */
    if (!wglglue_wglQueryPbuffer(context->hpbuffer,
                                    WGL_PBUFFER_WIDTH_ARB,
                                    &(context->width))) {
      if (warnonerrors || coin_glglue_debug()) {
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "Couldn't query the pbuffer width.");
      }
      return FALSE;
    }

    if (!wglglue_wglQueryPbuffer(context->hpbuffer,
                                 WGL_PBUFFER_HEIGHT_ARB,
                                 &(context->height))) {
      if (warnonerrors || coin_glglue_debug()) {
        cc_debugerror_postwarning("wglglue_context_create_pbuffer",
                                  "Couldn't query the pbuffer height.");
      }
      return FALSE;
    }
  }

  if (coin_glglue_debug()) {
    cc_debugerror_postinfo("wglglue_context_create_pbuffer",
                           "success creating pbuffer, HGLRC==%p",
                           context->wglcontext);
  }

  return TRUE;
}

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

/* Create and return a handle to an offscreen OpenGL buffer.

   Where p-buffer support is available that will be used instead of a
   standard offscreen WGL context, as it should render much faster
   (due to hardware acceleration).

   See:
     http://www.oss.sgi.com/projects/ogl-sample/registry/ARB/wgl_pbuffer.txt
   Or the older version:
     http://www.oss.sgi.com/projects/ogl-sample/registry/EXT/wgl_pbuffer.txt
*/
void *
wglglue_context_create_offscreen(unsigned int width, unsigned int height)
{
  struct wglglue_contextdata * swctx, * pbctx;
  SbBool ispbuffer;

  if (coin_glglue_debug()) {
    cc_debugerror_postinfo("wglglue_context_create_offscreen",
                           "method called ");
  }

  swctx = wglglue_contextdata_init(width, height);
  assert(swctx);

  if (wglglue_context_create != NULL) {

    ispbuffer = wglglue_context_create == wglglue_context_create_pbuffer;

    /* don't warn if we fail to open a pbuffer context. we will try software  */
    if (wglglue_context_create(swctx, !ispbuffer)) { return swctx; }
    wglglue_contextdata_cleanup(swctx);

    /* fall back to a software context */
    if (ispbuffer) {
      if (coin_glglue_debug()) {
        cc_debugerror_postinfo("wglglue_context_create_offscreen",
                               "pbuffer failed. Trying software ");
      }
      swctx = wglglue_contextdata_init(width, height);
      assert(swctx);
      if (wglglue_context_create_software(swctx, TRUE)) { return swctx; }
      wglglue_contextdata_cleanup(swctx);
    }
    return NULL;
  }

  /* As there could possibly be no valid wgl context at this moment,
     we have to first make a context and set it current to be able
     to query pbuffer extension availability. */

  if (!wglglue_context_create_software(swctx, TRUE)) {
    wglglue_contextdata_cleanup(swctx);
    return NULL;
  }

  /* ok, so we can at least use a non-pbuffer offscreen context */
  wglglue_context_create = wglglue_context_create_software;

  /* developer or user can force pbuffer support off with this envvar */
  {
    const char * env = coin_getenv("COIN_WGLGLUE_NO_PBUFFERS");
    if (env && atoi(env) > 0) { return swctx; }
  }

  /* next, check if pbuffer support is available in the OpenGL
     library image */

  pbctx = wglglue_contextdata_init(width, height);
  assert(pbctx);

  /* attempt to create a pbuffer */
  if (!wglglue_context_create_pbuffer(pbctx, FALSE)) {
    wglglue_contextdata_cleanup(pbctx);
    return swctx;
  }

  /* pbuffers are really supported, kill the software offscreen
     context and use the pbuffer-enabled one */
  wglglue_contextdata_cleanup(swctx);

  wglglue_context_create = wglglue_context_create_pbuffer;

  return pbctx;
}

SbBool
wglglue_context_make_current(void * ctx)
{
  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;

  context->storedcontext = wglGetCurrentContext();
  if (context->storedcontext) { context->storeddc = wglGetCurrentDC(); }
  return wglMakeCurrent(context->memorydc, context->wglcontext) ? TRUE : FALSE;
}

void
wglglue_context_reinstate_previous(void * ctx)
{
  /* The previous context is stored and reset to make it possible to
     use an SoOffscreenRenderer from for instance an SoCallback node
     callback during SoGLRenderAction traversal, without the need for
     any extra book-keeping on the application side. */

  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;

  if (context->storedcontext && context->storeddc) {
    (void)wglMakeCurrent(context->storeddc, context->storedcontext);
    context->storedcontext = NULL;
  }
  else { (void)wglMakeCurrent(NULL, NULL); }
}

void
wglglue_context_destruct(void * ctx)
{
  /* FIXME: needs to call into the (as of yet unimplemented)
     "destructing GL context" handler. 20030310 mortene. */

  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;

  if (coin_glglue_debug()) {
    cc_debugerror_postinfo("wglglue_context_destruct",
                           "destroy context, HGLRC==%p", context->wglcontext);
  }

  wglglue_contextdata_cleanup(context);
}

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

void
wglglue_context_bind_pbuffer(void * ctx)
{
  BOOL ok;

  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;
  assert(wglglue_wglBindTexImageARB != NULL);
  assert(context->supports_render_to_texture);

  ok = wglglue_wglBindTexImageARB(context->hpbuffer, WGL_FRONT_LEFT_ARB);
  assert(ok);
  context->pbufferisbound = TRUE;
}

void
wglglue_context_release_pbuffer(void * ctx)
{
  BOOL ok;

  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;
  assert(wglglue_wglReleaseTexImageARB != NULL);

  ok = wglglue_wglReleaseTexImageARB(context->hpbuffer, WGL_FRONT_LEFT_ARB);
  assert(ok);
  context->pbufferisbound = FALSE;
}

SbBool
wglglue_context_pbuffer_is_bound(void * ctx)
{
  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;
  return context->pbufferisbound;
}

SbBool
wglglue_context_can_render_to_texture(void * ctx)
{
  struct wglglue_contextdata * context = (struct wglglue_contextdata *)ctx;
  return context->supports_render_to_texture;
}

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

/* If ctx does not point at a pbuffer context, but rather a "normal"
   offscreen context, it will return FALSE.

   Upon other error conditions, FALSE will also be returned.
*/
SbBool
wglglue_context_pbuffer_max(void * c, unsigned int * lims)
{
  BOOL ok;
  int attribvals[3], i;
  const int attribs[3] = {
    WGL_MAX_PBUFFER_WIDTH_ARB, WGL_MAX_PBUFFER_HEIGHT_ARB,
    WGL_MAX_PBUFFER_PIXELS_ARB
  };
  struct wglglue_contextdata * ctx = (struct wglglue_contextdata *)c;

  if (!ctx->hpbuffer) { return FALSE; }
  if (!wglglue_wglGetPixelFormatAttribiv) { return FALSE; }
 
  ok = wglglue_wglGetPixelFormatAttribiv(ctx->memorydc, ctx->pixelformat,
                                         0, /* main plane */
                                         3, attribs, attribvals);
  if (!ok) {
    if (coin_glglue_debug()) {
      DWORD dwError = GetLastError();
      cc_debugerror_postwarning("wglglue_context_pbuffer_max",
                                "wglGetPixelFormatAttribiv() failed with "
                                "error code %d.", dwError);
    }
    return FALSE;
  }

  for (i = 0; i < 3; i++) {
    assert(attribvals[i] >= 0);
    lims[i] = (unsigned int)attribvals[i];
  }
  return TRUE;
}

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

void wglglue_cleanup(void)
{
  wglglue_context_create = NULL;

  wglglue_wglCreatePbuffer = NULL;
  wglglue_wglGetPbufferDC = NULL;
  wglglue_wglReleasePbufferDC = NULL;
  wglglue_wglDestroyPbuffer = NULL;
  wglglue_wglQueryPbuffer = NULL;
  wglglue_wglBindTexImageARB = NULL;
  wglglue_wglReleaseTexImageARB = NULL;

  wglglue_wglChoosePixelFormat = NULL;
  wglglue_wglGetPixelFormatAttribiv = NULL;

  wglglue_wglGetExtensionsString = NULL;

  if (glhnd) { 
    cc_dl_close(glhnd); 
    glhnd = NULL; 
  }
  tried_fetching_handle = FALSE;
  attemptedextresolved = FALSE;

  /* FIXME: We should probably do an UnregisterClass() here (see
     wglglue_context_create_pbuffer()) -- but since I don't know the
     details of how to do this, I'll leave it to one of the Windows
     gurus...  20060207 kyrah */
}


#endif /* HAVE_WGL */

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