/* Direct3D Device
   (c) 1998 Lionel ULMER
   Copyright 2000-2001 TransGaming Technologies Inc.

   This files contains the MESA implementation of all the D3D devices that
   Wine supports. */

#include "config.h"

#include <string.h>

#include "windef.h"
#include "winerror.h"
#include "objbase.h"
#include "ddraw.h"
#include "d3d.h"
#include "bitmap.h"
#include "wine/debug.h"
#include "dsurface/main.h"
#include "dsurface/dib.h"
#include "dsurface/user.h"
#include "ddraw/main.h"
#include "ddraw/user.h"
#include "d3d_private.h"
#include "d3dvbuffer/main.h"

#include "mesa_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

/* Define this variable if you have an unpatched Mesa 3.0 (patches are available
   on Mesa's home page) or version 3.1b.

   Version 3.1b2 should correct this bug */
#undef HAVE_BUGGY_MESAGL

/* DRI-enhanced versions of mesa have these, standard versions don't. */
#ifndef GLX_RGBA_BIT
#define GLX_RGBA_BIT 1
#endif

#ifndef GLX_RGBA_TYPE
#define GLX_RGBA_TYPE 0x8014
#endif

#define D3DDPRIVATE(x) mesa_d3dd_private *odev=((mesa_d3dd_private*)(x)->private)
#define D3DFVF_TEXCOUNT(x) (((x) & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT)
#define D3DFVF_TEXCOORDSIZE(fvf,idx) ( ((((fvf)>>((idx)*2 + 16)) + 1) & 0x0003) + 1 )

#define DIB3DDATA(surf) (DIB_PRIV(surf)->dib.d3d_data)
#define DIBGLFMT(surf) (DIB3DDATA(surf)[0])
#define DIBGLTYPE(surf) (DIB3DDATA(surf)[1])
#define DIBGLLEVEL(surf) (DIB3DDATA(surf)[2])

#define TEXTURE_ID(surf) (DIB3DDATA(surf)[3])

/* TRANSGAMING_FIXME: Sadly, this doesn't *actually* tell us whether GLX 1.3 is supported */
#undef GLX_VERSION_1_3
/* Define if rendering into windows work so much better than rendering into pixmaps
 * that the mess, hacks, and potential conversion overhead that will result is worth it */
#define MESSY_PRIMARY

/* #define WIREFRAME */

#ifndef HAVE_GLEXT_PROTOTYPES
/* This is for non-OpenGL ABI compliant glext.h headers :-) */
typedef void (* PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat,
					 GLsizei width, GLenum format, GLenum type,
					 const GLvoid *table);
#endif

static const float identity_mat[16] = {
  1.0, 0.0, 0.0, 0.0,
  0.0, 1.0, 0.0, 0.0,
  0.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0
};

static const float zero4f[4] = { 0, 0, 0, 0 };
static const float one4f[4] = { 1, 1, 1, 1 };

static int GLXError;

/*******************************************************************************
 *				OpenGL static functions
 */
/* Make the preprocessor happy. */
#ifdef HAVE_OSMESA
#define ODEV_OSM_CTX(odev) ((odev)->osm_ctx)
#define ODEV_SURFACE(odev) ((odev)->surface)
#else
#define ODEV_OSM_CTX(odev) (NULL)
#define ODEV_SURFACE(odev) (NULL)
#endif

static void set_context(LPVOID iface) {
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  D3DDPRIVATE(This);
  Bool ret = False;

  TRACE("setting context (GLX %p drawable %ld, OSM %p buffer %p)\n",
	odev->glx_ctx, odev->drawable, ODEV_OSM_CTX(odev), ODEV_SURFACE(odev));

#ifdef GLX_VERSION_1_3
  if (odev->fbconfig)
    ret = glXMakeContextCurrent(gdi_display, odev->drawable, odev->drawable, odev->glx_ctx);
  else
#endif
  if (odev->vis)
    ret = glXMakeCurrent(gdi_display, odev->drawable, odev->glx_ctx);
#ifdef HAVE_OSMESA
  else
    ret = OSMesaMakeCurrent(odev->osm_ctx, odev->surface, GL_UNSIGNED_BYTE,
			    This->d.surface->surface_desc.dwWidth,
			    This->d.surface->surface_desc.dwHeight);
#endif
  if (ret == False) {
    ERR("Error in setting context (GLX %p drawable %ld, OSM %p buffer %p)!\n",
	odev->glx_ctx, odev->drawable, ODEV_OSM_CTX(odev), ODEV_SURFACE(odev));
  }
}

static void set_context_if_different(LPVOID iface)
{
    static LPVOID current_iface = NULL; /* XXX should be thread-local */

    if (iface != current_iface)
    {
	current_iface = iface;
	if (iface) set_context(iface);
    }
}

static void lock_device(LPVOID iface) {
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  D3DDPRIVATE(This);

  if (odev->using_primary) {
    /* should check DIBsection status (if it's not AuxMod, copy background from appropriate place) */
  }
  else if (odev->drawable)
  {
    /* Ensure that the Pixmap is up to date. This should result in InSync. */
    DIB_PRIV_VAR(surf_priv, This->d.surface);
    X11DRV_LockDIBSection2(surf_priv->dib.DIBsection, DIB_Status_GdiMod, FALSE);
  }

  /* do NOT call ENTER_GL() before the DIBSection stuff above is done,
   * or we will get deadlocks... */
  ENTER_GL();
  if (odev->drawable) {
    TRACE("waiting for X\n");
    glXWaitX();
  }
  TRACE("setting context\n");
  This->d.set_context(iface);
  TRACE("ready to roll\n");
}

static void unlock_device(LPVOID iface) {
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  D3DDPRIVATE(This);

  if (odev->using_primary) {
    /* should mark DIBsection as AuxMod */
    LEAVE_GL();
  }
  else if (odev->drawable)
  {
    /* Ensure that the image will be copied from the Pixmap.
     * If we are InSync, this changes state to GdiMod and protects the image.
     */
    glFinish();
    XSync(gdi_display, False);
    /* must call LEAVE_GL() before the DIBSection stuff below,
     * or we will get deadlocks... */
    LEAVE_GL();
    X11DRV_UnlockDIBSection2(DIB_PRIV(This->d.surface)->dib.DIBsection, TRUE);
  }
  else {
    LEAVE_GL();
  }

  TRACE("left GL\n");
}

static BOOL GLX_Flip(LPVOID iface, LPVOID data) {
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  D3DDPRIVATE(This);

  ENTER_GL();
  glXSwapBuffers(gdi_display, odev->drawable);
  LEAVE_GL();
  return TRUE;
}

static void fill_opengl_primcaps(D3DPRIMCAPS *pc)
{
  pc->dwSize = sizeof(*pc);
  pc->dwMiscCaps = D3DPMISCCAPS_CONFORMANT | D3DPMISCCAPS_CULLCCW | D3DPMISCCAPS_CULLCW |
    D3DPMISCCAPS_LINEPATTERNREP | D3DPMISCCAPS_MASKZ;
  pc->dwRasterCaps = D3DPRASTERCAPS_DITHER | D3DPRASTERCAPS_FOGRANGE | D3DPRASTERCAPS_FOGTABLE |
    D3DPRASTERCAPS_FOGVERTEX | D3DPRASTERCAPS_STIPPLE | D3DPRASTERCAPS_ZBIAS | D3DPRASTERCAPS_ZTEST;
  pc->dwZCmpCaps = 0xFFFFFFFF; /* All Z test can be done */
  pc->dwSrcBlendCaps  = 0xFFFFFFFF; /* FIXME: need REAL values */
  pc->dwDestBlendCaps = 0xFFFFFFFF; /* FIXME: need REAL values */
  pc->dwAlphaCmpCaps  = 0xFFFFFFFF; /* FIXME: need REAL values */
  pc->dwShadeCaps = 0xFFFFFFFF;     /* FIXME: need REAL values */
  pc->dwTextureCaps = D3DPTEXTURECAPS_ALPHA | D3DPTEXTURECAPS_BORDER | D3DPTEXTURECAPS_PERSPECTIVE |
    D3DPTEXTURECAPS_POW2 | D3DPTEXTURECAPS_TRANSPARENCY;
  pc->dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR | D3DPTFILTERCAPS_LINEARMIPLINEAR | D3DPTFILTERCAPS_LINEARMIPNEAREST |
    D3DPTFILTERCAPS_MIPLINEAR | D3DPTFILTERCAPS_MIPNEAREST | D3DPTFILTERCAPS_NEAREST;
  pc->dwTextureBlendCaps = 0xFFFFFFFF; /* FIXME: need REAL values */
  pc->dwTextureAddressCaps = D3DPTADDRESSCAPS_BORDER | D3DPTADDRESSCAPS_CLAMP | D3DPTADDRESSCAPS_WRAP;
  pc->dwStippleWidth = 32;
  pc->dwStippleHeight = 32;
}

static void fill_opengl_caps(D3DDEVICEDESC *d1, D3DDEVICEDESC *d2)
{
  /* GLint maxlight; */

  d1->dwSize  = sizeof(*d1);
  d1->dwFlags = D3DDD_DEVCAPS | D3DDD_BCLIPPING | D3DDD_COLORMODEL | D3DDD_DEVICERENDERBITDEPTH | D3DDD_DEVICEZBUFFERBITDEPTH
    | D3DDD_LIGHTINGCAPS | D3DDD_LINECAPS | D3DDD_MAXBUFFERSIZE | D3DDD_MAXVERTEXCOUNT | D3DDD_TRANSFORMCAPS | D3DDD_TRICAPS;
  d1->dcmColorModel = D3DCOLOR_RGB;
  d1->dwDevCaps = D3DDEVCAPS_CANRENDERAFTERFLIP | D3DDEVCAPS_DRAWPRIMTLVERTEX | D3DDEVCAPS_EXECUTESYSTEMMEMORY |
    D3DDEVCAPS_EXECUTEVIDEOMEMORY | D3DDEVCAPS_FLOATTLVERTEX | D3DDEVCAPS_TEXTURENONLOCALVIDMEM | D3DDEVCAPS_TEXTURESYSTEMMEMORY |
      D3DDEVCAPS_TEXTUREVIDEOMEMORY | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY | D3DDEVCAPS_TLVERTEXVIDEOMEMORY |
      D3DDEVCAPS_HWTRANSFORMANDLIGHT | D3DDEVCAPS_HWRASTERIZATION;
  d1->dtcTransformCaps.dwSize = sizeof(D3DTRANSFORMCAPS);
  d1->dtcTransformCaps.dwCaps = D3DTRANSFORMCAPS_CLIP;
  d1->bClipping = TRUE;
  d1->dlcLightingCaps.dwSize = sizeof(D3DLIGHTINGCAPS);
  d1->dlcLightingCaps.dwCaps = D3DLIGHTCAPS_DIRECTIONAL | D3DLIGHTCAPS_PARALLELPOINT | D3DLIGHTCAPS_POINT | D3DLIGHTCAPS_SPOT;
  d1->dlcLightingCaps.dwLightingModel = D3DLIGHTINGMODEL_RGB;
  d1->dlcLightingCaps.dwNumLights = 8; /* glGetIntegerv(GL_MAX_LIGHTS, &maxlight); d1->dlcLightingCaps.dwNumLights = maxlight; */
  fill_opengl_primcaps(&(d1->dpcLineCaps));
  fill_opengl_primcaps(&(d1->dpcTriCaps));
  d1->dwDeviceRenderBitDepth  = DDBD_16;
  d1->dwDeviceZBufferBitDepth = DDBD_16;
  d1->dwMaxBufferSize = 0;
  d1->dwMaxVertexCount = 65536;
  d1->dwMinTextureWidth  = 1;
  d1->dwMinTextureHeight = 1;
  d1->dwMaxTextureWidth  = 256; /* This is for Mesa on top of Glide (in the future :-) ) */
  d1->dwMaxTextureHeight = 256; /* This is for Mesa on top of Glide (in the future :-) ) */
  d1->dwMinStippleWidth  = 1;
  d1->dwMinStippleHeight = 1;
  d1->dwMaxStippleWidth  = 32;
  d1->dwMaxStippleHeight = 32;

  /* Some of these values taken from: http://perso.club-internet.fr/lyritron/infos_geforceSDR_main.htm */
  d1->dwMaxTextureRepeat  = 256;
  d1->dwMaxTextureAspectRatio = 0;
  d1->dwMaxAnisotropy = 1;

  d1->dvGuardBandLeft = -4096.0;
  d1->dvGuardBandTop = -4096.0;
  d1->dvGuardBandRight = 4095.0;
  d1->dvGuardBandBottom = 4095.0;

  d1->dvExtentsAdjust = 0;

  /* D3DSTENCILOP_DECR and D3DSTENCILOP_INCR should be supported if
   * GL_EXT_stencil_wrap is present. */
  d1->dwStencilCaps = D3DSTENCILOP_DECRSAT | D3DSTENCILOP_INCRSAT
      | D3DSTENCILOP_INVERT | D3DSTENCILOP_KEEP | D3DSTENCILOP_REPLACE
      | D3DSTENCILOP_ZERO;

  d1->dwFVFCaps = D3DFVFCAPS_TEXCOORDCOUNTMASK;
  d1->dwTextureOpCaps = 0xFFFFFFFF; /* Claim we can do it all for now */
  d1->wMaxTextureBlendStages = NUM_TEXTURE_STAGES;
  d1->wMaxSimultaneousTextures = 2; /* XXX */

  d2->dwSize  = sizeof(*d2);
  d2->dwFlags = 0;
}

static void fill_device_capabilities(IDirectDrawImpl* ddraw) {
#if 0
  x11_dd_private *private = (x11_dd_private *) ddraw->d->private;
  const char *ext_string;
  Mesa_DeviceCapabilities *devcap;

  private->device_capabilities = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(Mesa_DeviceCapabilities));
  devcap = (Mesa_DeviceCapabilities *) private->device_capabilities;

  ENTER_GL();
  ext_string = glGetString(GL_EXTENSIONS);
  /* Query for the ColorTable Extension */
  if (ext_string && strstr(ext_string, "GL_EXT_paletted_texture")) {
    devcap->ptr_ColorTableEXT = (PFNGLCOLORTABLEEXTPROC) glXGetProcAddressARB("glColorTableEXT");
    TRACE("Color table extension supported (function at %p)\n", devcap->ptr_ColorTableEXT);
  } else {
    TRACE("Color table extension not found.\n");
  }
  LEAVE_GL();
#endif
}

static void MESA_fake_glActiveTextureARB(GLenum texture)
{
    assert(texture == GL_TEXTURE0_ARB);
}

static void MESA_fake_glClientActiveTextureARB(GLenum texture)
{
    assert(texture == GL_TEXTURE0_ARB);
}

static void MESA_fake_glMultiTexCoord2fARB(GLenum target, GLfloat u, GLfloat v)
{
    assert(target == GL_TEXTURE0_ARB);
    glTexCoord2f(u, v);
}

static void MESA_fake_glSecondaryColor3ubEXT(const GLfloat* v)
{
}

static void MESA_InitExtensionFunctions(mesa_d3dd_private* priv)
{
    struct extension_func
    {
	const char *name;
	ptrdiff_t pfn_offset;
	void *default_fn;
    };

#define EF(x)  { #x, offsetof(mesa_d3dd_private, x), NULL }
#define EFD(x) { #x, offsetof(mesa_d3dd_private, x), MESA_fake_##x }

    static const struct extension_func extension_funcs[] =
	{
	    EFD(glActiveTextureARB),
	    EFD(glClientActiveTextureARB),
	    EFD(glMultiTexCoord2fARB),
	    EFD(glSecondaryColor3ubEXT),
	    EF(glSecondaryColorPointerEXT)
	};

#undef EF
#undef EFD

    int i;

    for (i = 0; i < sizeof(extension_funcs)/sizeof(extension_funcs[0]); i++)
    {
#ifdef HAVE_GLX_GETPROCADDRESS
	void *func = glXGetProcAddressARB(extension_funcs[i].name);
#else
	void *func = NULL;
#endif
	if (func == NULL) func = extension_funcs[i].default_fn;

	*(void **)((char *)priv + extension_funcs[i].pfn_offset) = func;
    }
}

static const char *const MESA_extension_names[] =
{
    "GL_ARB_multitexture",
    "GL_ARB_texture_compression",
    "GL_ARB_texture_cube_map",
    "GL_ARB_texture_env_add",
    "GL_EXT_blend_func_separate",
    "GL_EXT_fog_coord",
    "GL_EXT_secondary_color",
    "GL_EXT_stencil_wrap",
    "GL_EXT_texture_compression_s3tc",
    "GL_EXT_texture_env_add",
    "GL_EXT_texture_env_combine",
    "GL_EXT_texture_lod_bias",
    "GL_EXT_vertex_weighting",
    "GL_NV_blend_square",
    "GL_S3_s3tc",
    "GL_EXT_clip_volume_hint",
    "GL_ARB_texture_env_combine",
    "GL_EXT_texture_env_dot3"

};

/* extensions_string is what you get from glGetString. */
static BOOL MESA_IsExtensionPresent(const char *extension_name,
				    const char *extensions_string)
{
    size_t name_len = strlen(extension_name);
    const char *start = extensions_string;

    while (TRUE)
    {
	const char *possible = strstr(start, extension_name);
	if (possible == NULL) return FALSE;

	if ((possible == extensions_string || isspace(possible[-1]))
	    && ((possible + name_len)[0] == '\0'
		|| isspace((possible+name_len)[0])))
	{
	    return TRUE;
	}

	start = possible + 1;
    }
}

/* Assumes that the context has been made current. */
static void MESA_InitExtensions(mesa_d3dd_private* priv)
{
    const char* extensions = glGetString(GL_EXTENSIONS);
    int i;

    /* This may explain why certain extensions are missing. */
    TRACE("IsDirect: %s\n",
	  glXIsDirect(gdi_display, priv->glx_ctx) ? "yes" : "NO");

    for (i = 0; i < NUM_GL_EXTENSIONS; i++)
    {
	priv->extension_present[i]
	    = MESA_IsExtensionPresent(MESA_extension_names[i], extensions);

	TRACE("%s: %s\n", MESA_extension_names[i],
	      priv->extension_present[i] ? "yes" : "NO");

    }

    MESA_InitExtensionFunctions(priv);
}

static BOOL MESA_UseExtension(mesa_d3dd_private* priv, gl_extensions extension)
{
    return priv->extension_present[extension];
}

int d3denum_OpenGL(LPD3DENUMDEVICESCALLBACK cb, LPVOID context) {
  D3DDEVICEDESC	d1,d2;
  TRACE(" Enumerating OpenGL D3D2 device (IID %s).\n", debugstr_guid(&IID_D3DDEVICE2_OpenGL));
  fill_opengl_caps(&d1, &d2);
  return cb((void*)&IID_D3DDEVICE2_OpenGL,"WINE Direct3D2 using OpenGL","direct3d",&d1,&d2,context);
}

static void fill_opengl_caps7(D3DDEVICEDESC7 *d)
{
    D3DDEVICEDESC soft, hard;
    soft.dwSize = sizeof(soft);
    hard.dwSize = sizeof(hard);
    fill_opengl_caps(&soft, &hard);

    d->dwDevCaps	= soft.dwDevCaps;
    d->dpcLineCaps	= soft.dpcLineCaps;
    d->dpcTriCaps	= soft.dpcTriCaps;
    d->dwDeviceRenderBitDepth = soft.dwDeviceRenderBitDepth;
    d->dwDeviceZBufferBitDepth = soft.dwDeviceZBufferBitDepth;
    d->dwMinTextureWidth = soft.dwMinTextureWidth;
    d->dwMinTextureHeight = soft.dwMinTextureHeight;
    d->dwMaxTextureWidth = soft.dwMaxTextureWidth;
    d->dwMaxTextureHeight = soft.dwMaxTextureHeight;
    d->dwMaxTextureRepeat = soft.dwMaxTextureRepeat;
    d->dwMaxTextureAspectRatio = soft.dwMaxTextureAspectRatio;
    d->dwMaxAnisotropy = soft.dwMaxAnisotropy;
    d->dvGuardBandLeft = soft.dvGuardBandLeft;
    d->dvGuardBandTop = soft.dvGuardBandTop;
    d->dvGuardBandRight = soft.dvGuardBandRight;
    d->dvGuardBandBottom = soft.dvGuardBandBottom;
    d->dvExtentsAdjust = soft.dvExtentsAdjust;
    d->dwStencilCaps = soft.dwStencilCaps;
    d->dwFVFCaps = soft.dwFVFCaps;
    d->dwTextureOpCaps = soft.dwTextureOpCaps;
    d->wMaxTextureBlendStages = soft.wMaxTextureBlendStages;
    d->wMaxSimultaneousTextures = soft.wMaxSimultaneousTextures;
    d->dwMaxActiveLights = 8;
    d->dvMaxVertexW = 1.0; /* XXX */
    d->deviceGUID = IID_IDirect3DTnLHalDevice;
    d->wMaxUserClipPlanes = D3DMAXUSERCLIPPLANES;
    d->wMaxVertexBlendMatrices = 0;
    d->dwVertexProcessingCaps = D3DVTXPCAPS_TEXGEN | D3DVTXPCAPS_VERTEXFOG
	| D3DVTXPCAPS_DIRECTIONALLIGHTS | D3DVTXPCAPS_POSITIONALLIGHTS
	| D3DVTXPCAPS_LOCALVIEWER;
    d->dwReserved1 = 0;
    d->dwReserved2 = 0;
    d->dwReserved3 = 0;
    d->dwReserved4 = 0;
}

int d3d7enum_OpenGL(LPD3DENUMDEVICESCALLBACK7 cb, LPVOID context) {
  D3DDEVICEDESC7 d;
  TRACE(" Enumerating OpenGL D3D device.\n");
  fill_opengl_caps7(&d);
  return cb("WINE Direct3D using OpenGL","direct3d",&d,context);
}

static int GLXErrorHandler(Display *dpy, XErrorEvent *event)
{
  GLXError = 1;
  return 0;
}

static void GLX_Destroy(IDirect3DDeviceImpl*This)
{
  D3DDPRIVATE(This);

  set_context_if_different(NULL); /* to clear that static var */

  if (odev->glx_ctx) {
    glXDestroyContext(gdi_display, odev->glx_ctx);
    odev->glx_ctx = 0;
  }
#ifdef HAVE_OSMESA
  if (odev->osm_ctx) {
    OSMesaDestroyContext(odev->osm_ctx);
    odev->osm_ctx = 0;
    HeapFree(GetProcessHeap(), 0, odev->surface);
    OSMesaMakeCurrent(NULL, NULL, GL_UNSIGNED_BYTE, 0, 0);
  }
#endif
#ifdef GLX_VERSION_1_3
  if (odev->fbconfig) {
    if (odev->drawable) {
#ifdef MESSY_PRIMARY
      if (This->d.surface->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)
        glXDestroyWindow(gdi_display, odev->drawable);
      else
#endif
        glXDestroyPixmap(gdi_display, odev->drawable);
    }
    XFree(odev->fbconfig);
    odev->fbconfig = NULL;
    glXMakeContextCurrent(gdi_display, None, None, 0);
  }
  else
#endif
  if (odev->vis) {
    if (odev->drawable
#ifdef MESSY_PRIMARY
        && !This->d.surface->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE
#endif
       )
      glXDestroyGLXPixmap(gdi_display, odev->drawable);
    XFree(odev->vis);
    odev->vis = NULL;
    glXMakeCurrent(gdi_display, None, 0);
  }
  odev->drawable = 0;
}

#if 0 /* This can be activated for debugging */
static void display_glx_visual_info(XVisualInfo *vis)
{
    static const int attribs[8] =
    { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, GLX_GREEN_SIZE, GLX_BLUE_SIZE,
      GLX_ALPHA_SIZE, GLX_DEPTH_SIZE, GLX_STENCIL_SIZE };

    int vals[8];

    int i;

    if (vis == NULL)
    {
	TRACE("NULL glx visual\n");
	return;
    }

    for (i = 0; i < 8; i++)
	glXGetConfig(gdi_display, vis, attribs[i], &vals[i]);

    TRACE("rgba %d doublebuffer %d\n", vals[0], vals[1]);
    TRACE("red %d green %d blue %d alpha %d depth %d stencil %d\n",
	  vals[2], vals[3], vals[4], vals[5], vals[6], vals[7]);
}
#endif /* 0 */

static HRESULT
MESA_standard_texture_combiner(mesa_d3dd_private* priv, DWORD dwStage,
			       D3DTEXTURESTAGESTATETYPE type, DWORD dwState);
static HRESULT
MESA_env_combine_texture_combiner(mesa_d3dd_private* odev, DWORD dwStage,
				  D3DTEXTURESTAGESTATETYPE type,
				  DWORD dwState);

static int common_inst_OpenGL(
    REFCLSID rguid, IDirectDrawSurfaceImpl* surface,
    IDirect3DDeviceImpl* device, IDirect3DImpl* d3d
) {
  D3DDPRIVATE(device);
  IDirectDrawSurfaceImpl* psurf;
  Bool direct = True;
  int i;

  psurf = surface;
#ifdef MESSY_PRIMARY
  /* if we're created for a back buffer, find the primary surface */
  while (psurf->surface_owner != NULL)
      psurf = psurf->surface_owner;

  /* if it isn't a primary surface, just go back to the original */
  if (!(psurf->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE))
  {
      psurf = surface;
      /* direct = False; */
  }
#endif


  if (surface->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)
    TRACE("This is a primary surface\n");
  else if (psurf->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)
    TRACE("This is a backbuffer surface\n");
  else
    TRACE("This is an offscreen surface\n");

  device->d.d3d = d3d;
  device->d.surface = surface;
  device->d.set_context = set_context;
  device->d.lock_device = lock_device;
  device->d.unlock_device = unlock_device;

  TRACE("Creating OpenGL device for surface %p\n", surface);
  /* Create the OpenGL context */
  ENTER_GL();
  XSync(gdi_display, False);
  {
#ifdef GLX_VERSION_1_3
  /* First get the correct framebuffer configuration */
    int al[64], ap = 0, nr;

    al[ap++] = GLX_DRAWABLE_TYPE; al[ap++] = GLX_PIXMAP_BIT; /* WINDOW, PIXMAP, or PBUFFER */
    al[ap++] = GLX_RENDER_TYPE;   al[ap++] = GLX_RGBA_BIT; /* RGA or COLOR_INDEX */
    al[ap++] = GLX_RED_SIZE;      al[ap++] = 1;
    al[ap++] = GLX_GREEN_SIZE;    al[ap++] = 1;
    al[ap++] = GLX_BLUE_SIZE;     al[ap++] = 1;
    al[ap++] = None;
/* other attributes to consider: GLX_DOUBLEBUFFER, GLX_STEREO, GLX_X_RENDERABLE */

    odev->fbconfig = glXChooseFBConfig(gdi_display, DefaultScreen(gdi_display), al, &nr);

    if (!odev->fbconfig) {
      ERR("No GLX 1.3 frame buffer configuration available\n");
    }
  }
  if (odev->fbconfig) {
    /* Create the context */
    odev->glx_ctx = glXCreateNewContext(gdi_display, odev->fbconfig[0], GLX_RGBA_TYPE, NULL, direct);
  }
  else { /* fall back to GLX 1.2 */
#endif
    /* First get the correct visual */
    int num;
    XVisualInfo template;

    template.visualid = XVisualIDFromVisual(visual);
    odev->vis = XGetVisualInfo(gdi_display, VisualIDMask, &template, &num);

    odev->glx_ctx = glXCreateContext(gdi_display, odev->vis, NULL, direct);
  }

  if (odev->glx_ctx == NULL) {
    LEAVE_GL();
    ERR("Error in GLX context creation !\n");
    return 0;
  } else
    TRACE("GLX context created (%p)\n", odev->glx_ctx);

#ifdef HAVE_OSMESA
  odev->surface = surface->surface_desc.lpSurface;
#endif

#ifdef MESSY_PRIMARY
  if (psurf->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) {
    /* but hWnd will be NULL for the DDSCL_NORMAL */
    WND *tmpWnd = WIN_FindWndPtr(psurf->get_display_window(psurf));
    Window draw = get_client_window(tmpWnd);
    TRACE("Owner WND: %p Drawable: %ld\n", tmpWnd, draw);
    WIN_ReleaseWndPtr(tmpWnd);
    if (!draw) {
	TRACE("no drawable, host off desktop\n");
	tmpWnd = WIN_FindWndPtr(GetDesktopWindow());
	draw = get_client_window(tmpWnd);
	TRACE("Owner WND: %p Drawable: %ld\n", tmpWnd, draw);
	WIN_ReleaseWndPtr(tmpWnd);
    }

#ifdef GLX_VERSION_1_3
    if (odev->fbconfig)
      odev->drawable = glXCreateWindow(gdi_display, odev->fbconfig[0], draw, NULL);
    else
#endif
      odev->drawable = draw;

    /* Add Flip handlers */
    /* FIXME: maybe also add aux copy handlers to the DIBsection parts */
    psurf->aux_ctx = device;
    psurf->aux_flip = GLX_Flip;
    odev->using_primary = TRUE;
  } else
#endif
  {
    BITMAPOBJ *bmp;
    Pixmap draw;
    HRESULT hr;

    /* Ensure that the surface has a DIBsection if it needs one - ie, if we're
     * rendering into an offscreen surface via an XPixmap */
    hr = surface->lazy_alloc_dibsection(surface);
    if (FAILED(hr)) return 0;

    /* If this is a secondary surface, grab the drawable from the surface's DIB section */
    bmp = (BITMAPOBJ *) GDI_GetObjPtr(DIB_PRIV(surface)->dib.DIBsection, BITMAP_MAGIC);
    draw = (Pixmap)bmp->physBitmap;
    GDI_ReleaseObj(DIB_PRIV(surface)->dib.DIBsection);
    /* this is not likely to be accelerated, but just for testing... */
#ifdef GLX_VERSION_1_3
    if (odev->fbconfig)
      odev->drawable = glXCreatePixmap(gdi_display, odev->fbconfig[0], draw, NULL);
    else
#endif
      odev->drawable = glXCreateGLXPixmap(gdi_display, odev->vis, draw);
    TRACE("Created GLX pixmap %ld from DIBsection pixmap %ld\n", odev->drawable, draw);
  }
  if (!odev->drawable) {
    LEAVE_GL();
    ERR("Error in GLX pixmap creation!\n");
    return 0;
  }

  odev->view_changed = TRUE;
  odev->transform_changed = TRUE;

  odev->rs.src = GL_ONE;
  odev->rs.dst = GL_ZERO;
  odev->rs.mag = GL_NEAREST;
  odev->rs.min = GL_NEAREST;
  odev->vt     = 0;

  odev->color_vertex = TRUE;
  odev->specular = FALSE;
  odev->diffuse_source = D3DMCS_COLOR1;
  odev->specular_source = D3DMCS_COLOR2;
  odev->ambient_source = D3DMCS_MATERIAL;
  odev->emissive_source = D3DMCS_MATERIAL;

  odev->fog_enable = FALSE;

  odev->clip_enable = TRUE;
  odev->vertex_fog_mode = D3DFOG_NONE;
  odev->table_fog_mode = D3DFOG_NONE;

  odev->alpha_ref = 0; /* ??? not documented */
  odev->alpha_func = GL_ALWAYS;
  odev->src_blend = GL_ONE;
  odev->dst_blend = GL_ZERO;

  odev->stencil_fail_op = GL_KEEP;
  odev->stencil_zfail_op = GL_KEEP;
  odev->stencil_pass_op = GL_KEEP;
  odev->stencil_func = GL_ALWAYS;
  odev->stencil_ref = 0;
  odev->stencil_read_mask = 0xFFFFFFFF;

  memcpy(&odev->world_mat, identity_mat, 16 * sizeof(float));
  memcpy(&odev->view_mat , identity_mat, 16 * sizeof(float));
  memcpy(&odev->proj_mat , identity_mat, 16 * sizeof(float));

  for (i = 0; i < NUM_TEXTURE_STAGES; i++)
  {
      odev->texstage[i].enabled = FALSE;
      odev->texstage[i].colorop = D3DTOP_DISABLE;
      odev->texstage[i].colorarg1 = D3DTA_TEXTURE;
      odev->texstage[i].colorarg2 = D3DTA_CURRENT;
      odev->texstage[i].alphaop = D3DTOP_DISABLE;
      odev->texstage[i].alphaarg1 = D3DTA_TEXTURE;
      odev->texstage[i].alphaarg2 = D3DTA_CURRENT;
      odev->texstage[i].coord_idx = i;
      odev->texstage[i].mipfilter = D3DTFP_NONE;
      odev->texstage[i].minfilter = D3DTFN_POINT;
      odev->texstage[i].u_addr = D3DTADDRESS_WRAP;
      odev->texstage[i].v_addr = D3DTADDRESS_WRAP;
      odev->texstage[i].lodbias = 0;
  }

  odev->texstage[0].enabled = TRUE;
  odev->texstage[0].colorop = D3DTOP_MODULATE;
  odev->texstage[0].alphaop = D3DTOP_SELECTARG1;

  /* Initialisation */
glx_context_set:
  {
    int (*WineXHandler)(Display *, XErrorEvent *);
    TRACE("Setting GLX context\n");
    WineXHandler = XSetErrorHandler(GLXErrorHandler);
    GLXError = 0;
    device->d.set_context(device);
    TRACE("Current context set\n");
    XSetErrorHandler(WineXHandler);
  }
  if (GLXError) {
    /* argh! */
    if (odev->vis) {
      if (direct) {
        ERR("GLXPixmap direct rendering rejected by OpenGL implementation, switching to indirect rendering\n");
        direct = False;
        glXDestroyContext(gdi_display, odev->glx_ctx);
#ifdef GLX_VERSION_1_3
        if (odev->fbconfig) {
          glXMakeContextCurrent(gdi_display, None, None, 0);
          odev->glx_ctx = glXCreateNewContext(gdi_display, odev->fbconfig[0], GLX_RGBA_TYPE, NULL, direct);
        }
        else
#endif
        {
          glXMakeCurrent(gdi_display, None, 0);
          odev->glx_ctx = glXCreateContext(gdi_display, odev->vis, NULL, direct);
        }
        goto glx_context_set;
      } else {
        ERR("GLXPixmap rendering rejected by OpenGL implementation\n");
      }
    }
    ERR("No off-screen GLX surfaces are supported by this OpenGL implementation!\n");
    GLX_Destroy(device);
#ifdef HAVE_OSMESA
    /* scrap GLX, let's go for OSMesa instead, then...
     * it'll be really slow, but better than nothing... */
    ERR("Creating OSMesa surface instead, but no hardware acceleration is possible!\n");
    odev->osm_ctx = OSMesaCreateContext(OSMESA_RGBA, NULL);
    /* OSMesa only supports 32-bit RGBA surfaces for now,
     * so we can't render directly to the DirectDrawSurface
     * (besides, the OSMesa surface is physically upside-down) */
    odev->surface = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
			      surface->surface_desc.dwWidth * surface->surface_desc.dwHeight * 4);
    if (!odev->surface) {
      ERR("Out of memory for OSMesa surface\n");
      LEAVE_GL();
      return 0;
    }
    TRACE("Setting OSMesa context\n");
    device->d.set_context(device);
    TRACE("Current context set\n");
#else
    LEAVE_GL();
    return 0;
#endif
  }

  device->d.set_context = set_context_if_different;

  odev->color_array = VirtualAlloc(NULL, 64*1024, MEM_RESERVE|MEM_COMMIT,
				   PAGE_READWRITE);

  MESA_InitExtensions(odev);

  /* GLX sets up double-buffered pixmaps, but offscreen surfaces are supposed
   * to be single-buffered. Use the front buffer and forget about the back. */
#ifdef MESSY_PRIMARY
  if (!(psurf->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE))
#endif
      glDrawBuffer(GL_FRONT);

  glFrontFace(GL_CW);
  glEnable(GL_CULL_FACE);
  glDisable(GL_DITHER);
  glEnable(GL_SCISSOR_TEST);

  device->d.light = TRUE;
  glEnable(GL_LIGHTING);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zero4f);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
  glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);

  /* With lighting off, glColor sets the final color for the vertex.
   * This is what we want for D3DFVF_DIFFUSE (prelit) vertices.
   * Otherwise glColor is ignored.
   * glMaterial is used with lighting on. */
  glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, zero4f);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, zero4f);
  glColor4fv(one4f);

#ifdef WIREFRAME
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif

  glEnable(GL_TEXTURE_2D); /* texture stage 0 is enabled */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 8);

  glDepthFunc(GL_LEQUAL);
  /* Docs say enable depth test iff there is an attached ZBuffer surface, but
   * that appears to be wrong. (RR5). */

  {
      /* Get the drawable width/height by retrieving the initival viewport
       * settings. */
      GLint viewport[4];
      glGetIntegerv(GL_VIEWPORT, viewport);
      odev->drawable_width = viewport[2];
      odev->drawable_height = viewport[3];
  }

  if (MESA_UseExtension(odev, ARB_multitexture))
      glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &odev->max_gl_textures);
  else
      odev->max_gl_textures = 1;

  if (MESA_UseExtension(odev, EXT_texture_env_combine))
  {
      odev->texture_combiner = MESA_env_combine_texture_combiner;
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);

      /* Setup the alphaop as SELECTARG1. (The other defaults are fine.) */
      glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
  }
  else
  {
      odev->texture_combiner = MESA_standard_texture_combiner;
  }

  glGetIntegerv(GL_MAX_LIGHTS, (GLint*) &odev->num_lights);
  odev->lights = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
			   odev->num_lights * sizeof(struct mesa_light));

  /* TODO: set up the default fog parameters */
  /* TODO: glHint */

  LEAVE_GL();

  fill_device_capabilities(d3d->ddraw);

  TRACE("OpenGL device created \n");
  return 1;
}

int d3dinst_OpenGL(
    REFCLSID rguid, IDirectDrawSurfaceImpl* surface,
    IDirect3DDeviceImpl** device, IDirect3DImpl* d3d,
    LPVOID vtable
) {
  if (/* Default device */
      (rguid == NULL) ||
      /* HAL Device */
      (!memcmp(&IID_IDirect3DHALDevice,rguid,sizeof(IID))) ||
      /* TnLHal Device */
      (!memcmp(&IID_IDirect3DTnLHalDevice,rguid,sizeof(IID))) ||
      /* OpenGL Device */
      (!memcmp(&IID_D3DDEVICE2_OpenGL,rguid,sizeof(IID)))) {

    *device = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirect3DDeviceImpl));
    (*device)->private = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(mesa_d3dd_private));
    (*device)->ref = 1;
    ICOM_VTBL(*device) = vtable;
    (*device)->viewport_list = NULL;
    (*device)->current_viewport = NULL;

    if (!common_inst_OpenGL(rguid, surface, *device, d3d)) {
      HeapFree(GetProcessHeap(), 0, *device);
      *device = NULL;
      return 0;
    }
    return 1;
  }
  FIXME("bad IID %s\n",debugstr_guid(rguid));
  /* This is not the OpenGL UID */
  return 0;
}

int d3d7inst_OpenGL(
    REFCLSID rguid, IDirectDrawSurfaceImpl* surface,
    IDirect3DDeviceImpl** device, IDirect3DImpl* d3d,
    LPVOID vtable
) {
  if (/* Default device */
      (rguid == NULL) ||
      /* HAL Device */
      (!memcmp(&IID_IDirect3DHALDevice,rguid,sizeof(IID))) ||
      /* TnLHal Device */
      (!memcmp(&IID_IDirect3DTnLHalDevice,rguid,sizeof(IID))) ||
      /* OpenGL Device */
      (!memcmp(&IID_D3DDEVICE2_OpenGL,rguid,sizeof(IID)))) {

    *device = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirect3DDevice7Impl));
    (*device)->private = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(mesa_d3dd_private));
    (*device)->ref = 1;
    ICOM_VTBL(*device) = vtable;

    if (!common_inst_OpenGL(rguid, surface, *device, d3d)) {
      HeapFree(GetProcessHeap(), 0, *device);
      *device = NULL;
      return 0;
    }
    return 1;
  }
  FIXME("bad IID %s\n",debugstr_guid(rguid));
  /* This is not the OpenGL UID */
  return 0;
}

static HRESULT enum_texture_format_OpenGL(LPD3DENUMTEXTUREFORMATSCALLBACK cb,
					  LPVOID context, int ver) {
  DDSURFACEDESC sdesc;
  LPDDSURFACEDESC psdesc = &sdesc;
  LPDDPIXELFORMAT pformat;

  /* Do the texture enumeration */
  sdesc.dwSize = sizeof(DDSURFACEDESC);
  sdesc.dwFlags = DDSD_PIXELFORMAT | DDSD_CAPS;
  sdesc.ddsCaps.dwCaps = DDSCAPS_TEXTURE;
  pformat = &(sdesc.ddpfPixelFormat);
  pformat->dwSize = sizeof(DDPIXELFORMAT);
  pformat->dwFourCC = 0;

  /* hack to support IDirect3DDevice3 and later,
   * which enumerates pixelformats instead of surfacedescs... */
  if (ver) psdesc = (LPDDSURFACEDESC)pformat;

  /* These are based on RGB emulation in 24/32 bpp. */

  TRACE("Enumerating GL_BGR 8/8/8.\n");
  pformat->dwFlags = DDPF_RGB;
  pformat->u1.dwRGBBitCount	= 32;
  pformat->u2.dwRBitMask	= 0x00FF0000;
  pformat->u3.dwGBitMask	= 0x0000FF00;
  pformat->u4.dwBBitMask	= 0x000000FF;
  pformat->u5.dwRGBAlphaBitMask	= 0x00000000;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("Enumerating GL_BGRA 8/8/8/8.\n");
  pformat->dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
  pformat->u1.dwRGBBitCount	= 32;
  pformat->u2.dwRBitMask	= 0x00FF0000;
  pformat->u3.dwGBitMask	= 0x0000FF00;
  pformat->u4.dwBBitMask	= 0x000000FF;
  pformat->u5.dwRGBAlphaBitMask	= 0xFF000000;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("Enumerating GL_RGB 5/6/5.\n");
  pformat->dwFlags = DDPF_RGB;
  pformat->u1.dwRGBBitCount	= 16;
  pformat->u2.dwRBitMask	= 0xF800;
  pformat->u3.dwGBitMask	= 0x07E0;
  pformat->u4.dwBBitMask	= 0x001F;
  pformat->u5.dwRGBAlphaBitMask = 0x0000;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("Enumerating GL_RGB 5/5/5.\n");
  pformat->dwFlags = DDPF_RGB;
  pformat->u1.dwRGBBitCount	= 16;
  pformat->u2.dwRBitMask	= 0x7C00;
  pformat->u3.dwGBitMask	= 0x03E0;
  pformat->u4.dwBBitMask	= 0x001F;
  pformat->u5.dwRGBAlphaBitMask = 0x0000;
  if (cb(psdesc, context) == 0)
      return DD_OK;

#if 0
  TRACE("Enumerating palette 4.\n");
  pformat->dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED4;
  pformat->u1.dwRGBBitCount	= 4;
  pformat->u2.dwRBitMask	= 0;
  pformat->u3.dwGBitMask	= 0;
  pformat->u4.dwBBitMask	= 0;
  pformat->u5.dwRGBAlphaBitMask	= 0;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("Enumerating palette 8.\n");
  pformat->dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8;
  pformat->u1.dwRGBBitCount	= 8;
  pformat->u2.dwRBitMask	= 0;
  pformat->u3.dwGBitMask	= 0;
  pformat->u4.dwBBitMask	= 0;
  pformat->u5.dwRGBAlphaBitMask	= 0;
  if (cb(psdesc, context) == 0)
      return DD_OK;
#endif

  /* We support this format with GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV. */
  TRACE("Enumerating ARGB 1/5/5/5.\n");
  pformat->dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
  pformat->u1.dwRGBBitCount	= 16;
  pformat->u2.dwRBitMask	= 0x7C00;
  pformat->u3.dwGBitMask	= 0x03E0;
  pformat->u4.dwBBitMask	= 0x001F;
  pformat->u5.dwRGBAlphaBitMask = 0x8000;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  /* We support this format with GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV. */
  TRACE("Enumerating ARGB 4/4/4/4.\n");
  pformat->dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
  pformat->u1.dwRGBBitCount	= 16;
  pformat->u2.dwRBitMask	= 0x0F00;
  pformat->u3.dwGBitMask	= 0x00F0;
  pformat->u4.dwBBitMask	= 0x000F;
  pformat->u5.dwRGBAlphaBitMask = 0xF000;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("Enumerating GL_LUMINOSITY 8.\n");
  pformat->dwFlags = DDPF_LUMINANCE;
  pformat->u1.dwLuminanceBitCount	= 8;
  pformat->u2.dwLuminanceBitMask	= 0xFF;
  pformat->u3.dwGBitMask		= 0x00;
  pformat->u4.dwBBitMask		= 0x00;
  pformat->u5.dwRGBAlphaBitMask		= 0x00;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("Enumerating GL_LUMINOSITY_ALPHA 8/8.\n");
  pformat->dwFlags = DDPF_LUMINANCE | DDPF_ALPHAPIXELS;
  pformat->u1.dwLuminanceBitCount	= 16;
  pformat->u2.dwLuminanceBitMask	= 0x00FF;
  pformat->u3.dwGBitMask		= 0x0000;
  pformat->u4.dwBBitMask		= 0x0000;
  pformat->u5.dwLuminanceAlphaBitMask	= 0xFF00;
  if (cb(psdesc, context) == 0)
      return DD_OK;

  TRACE("End of enumeration\n");

  return DD_OK;
}

/*******************************************************************************
 *				MESA IDirect3DDevice
 */
static ULONG WINAPI MESA_IDirect3DDeviceImpl_Release(LPDIRECT3DDEVICE iface)
{
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  D3DDPRIVATE(This);
  FIXME("(%p)->() decrementing from %lu.\n", This, This->ref );

  if (!--(This->ref)) {
    ENTER_GL();
    GLX_Destroy(This);
    LEAVE_GL();
    VirtualFree(odev->color_array, 0, MEM_DECOMMIT | MEM_RELEASE);
    HeapFree(GetProcessHeap(),0,This->private);
    This->private = NULL;
    IDirect3D_Release((LPDIRECT3D)This->d.d3d);
    HeapFree(GetProcessHeap(),0,This);
    return 0;
  }
  return This->ref;
}

static HRESULT WINAPI MESA_IDirect3DDeviceImpl_EnumTextureFormats(
    LPDIRECT3DDEVICE iface,LPD3DENUMTEXTUREFORMATSCALLBACK lpd3dEnumTextureProc,LPVOID lpArg)
{
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  TRACE("(%p)->(%p,%p): stub\n", This, lpd3dEnumTextureProc, lpArg);

  return enum_texture_format_OpenGL(lpd3dEnumTextureProc, lpArg, 0);
}

static HRESULT WINAPI MESA_IDirect3DDeviceImpl_BeginScene(
    LPDIRECT3DDEVICE iface
) {
  ICOM_THIS(IDirect3DDeviceImpl,iface);

  TRACE("(%p)->()\n", This);

  if (This->d.in_scene) return D3DERR_SCENE_IN_SCENE;

  This->d.lock_device(This);
  LEAVE_GL();

  This->d.in_scene = TRUE;

  return DD_OK;
}

HRESULT WINAPI MESA_IDirect3DDeviceImpl_EndScene(LPDIRECT3DDEVICE iface) {
  ICOM_THIS(IDirect3DDeviceImpl,iface);
#ifdef HAVE_OSMESA
  D3DDPRIVATE(This);
#endif

  TRACE("(%p)->()\n", This);

  if (!This->d.in_scene) return D3DERR_SCENE_NOT_IN_SCENE;

  ENTER_GL();
  This->d.unlock_device(This);
#ifdef HAVE_OSMESA
  if (odev->osm_ctx) {
    /* copy the OSMesa surface to the DirectDrawSurface */
    LPDIRECTDRAWSURFACE surf = (LPDIRECTDRAWSURFACE) This->d.surface;
    DDSURFACEDESC sdesc;

    IDirectDrawSurface_Lock(surf,NULL,&sdesc,DDLOCK_WRITEONLY,0);
    /* hardcode RGB 565 (16bpp) for now... */
    glReadPixels(0, 0, sdesc.dwWidth, sdesc.dwHeight, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, sdesc.lpSurface);
    IDirectDrawSurface_Unlock(surf,sdesc.lpSurface);
  }
#endif

  This->d.in_scene = FALSE;

  return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDeviceImpl_CreateExecuteBuffer(
    LPDIRECT3DDEVICE iface, LPD3DEXECUTEBUFFERDESC lpDesc,
    LPDIRECT3DEXECUTEBUFFER *lplpDirect3DExecuteBuffer, IUnknown *pUnkOuter
) {
    ICOM_THIS(IDirect3DDeviceImpl,iface);
    TRACE("(%p)->(%p,%p,%p)\n", This, lpDesc, lplpDirect3DExecuteBuffer, pUnkOuter);
    *lplpDirect3DExecuteBuffer = d3dexecutebuffer_create(This, lpDesc);
    return DD_OK;
}

/*******************************************************************************
 *				MESA Direct3DDevice VTable
 */
ICOM_VTABLE(IDirect3DDevice) OpenGL_d3ddvt =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  IDirect3DDeviceImpl_QueryInterface,
  IDirect3DDeviceImpl_AddRef,
  MESA_IDirect3DDeviceImpl_Release,
  IDirect3DDeviceImpl_Initialize,
  IDirect3DDeviceImpl_GetCaps,
  IDirect3DDeviceImpl_SwapTextureHandles,
  MESA_IDirect3DDeviceImpl_CreateExecuteBuffer,
  IDirect3DDeviceImpl_GetStats,
  IDirect3DDeviceImpl_Execute,
  IDirect3DDeviceImpl_AddViewport,
  IDirect3DDeviceImpl_DeleteViewport,
  IDirect3DDeviceImpl_NextViewport,
  IDirect3DDeviceImpl_Pick,
  IDirect3DDeviceImpl_GetPickRecords,
  MESA_IDirect3DDeviceImpl_EnumTextureFormats,
  IDirect3DDeviceImpl_CreateMatrix,
  IDirect3DDeviceImpl_SetMatrix,
  IDirect3DDeviceImpl_GetMatrix,
  IDirect3DDeviceImpl_DeleteMatrix,
  MESA_IDirect3DDeviceImpl_BeginScene,
  MESA_IDirect3DDeviceImpl_EndScene,
  IDirect3DDeviceImpl_GetDirect3D,
};

/*******************************************************************************
 *				MESA IDirect3DDevice2
 */
static HRESULT WINAPI MESA_IDirect3DDevice2Impl_GetCaps(
    LPDIRECT3DDEVICE2 iface, LPD3DDEVICEDESC lpdescsoft,
    LPD3DDEVICEDESC lpdeschard
) {
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  FIXME("(%p)->(%p,%p): stub\n", This, lpdescsoft, lpdeschard);
  fill_opengl_caps(lpdescsoft, lpdeschard);
  return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice2Impl_SetRenderState(
    LPDIRECT3DDEVICE2 iface, D3DRENDERSTATETYPE dwRenderStateType,
    DWORD dwRenderState
) {
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  D3DDPRIVATE(This);

  TRACE("(%p)->(%d,%ld)\n", This, dwRenderStateType, dwRenderState);

  /* Call the render state functions */
  set_render_state(dwRenderStateType, dwRenderState, &(odev->rs));

  return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice2Impl_SetLightState(
    LPDIRECT3DDEVICE2 iface, D3DLIGHTSTATETYPE dwLightStateType,
    DWORD dwLightState
) {
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  FIXME("(%p)->(%d,%08lx): stub\n", This, dwLightStateType, dwLightState);

  switch (dwLightStateType) {
  case D3DLIGHTSTATE_MATERIAL: {  /* 1 */
    IDirect3DMaterial2Impl* mat = (IDirect3DMaterial2Impl*) dwLightState;

    if (mat != NULL) {
      ENTER_GL();
      mat->activate(mat);
      LEAVE_GL();
    } else {
      TRACE("Zoups !!!\n");
    }
  } break;

  case D3DLIGHTSTATE_AMBIENT: {   /* 2 */
    float light[4];

    light[0] = ((dwLightState >> 16) & 0xFF) / 255.0;
    light[1] = ((dwLightState >>  8) & 0xFF) / 255.0;
    light[2] = ((dwLightState >>  0) & 0xFF) / 255.0;
    light[3] = 1.0;
    ENTER_GL();
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (float *) light);
    LEAVE_GL();
  } break;

#define UNSUP(x) case D3DLIGHTSTATE_##x: FIXME("unsupported D3DLIGHTSTATE_" #x "!\n");break;
  UNSUP(COLORMODEL);
  UNSUP(FOGMODE);
  UNSUP(FOGSTART);
  UNSUP(FOGEND);
  UNSUP(FOGDENSITY);
#undef UNSUP
  default:
    TRACE("Unexpected Light State Type\n");
    return DDERR_INVALIDPARAMS;
  }

  return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice2Impl_SetTransform(
    LPDIRECT3DDEVICE2 iface, D3DTRANSFORMSTATETYPE d3dts,
    LPD3DMATRIX lpmatrix
) {
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  D3DDPRIVATE(This);

  TRACE("(%p)->(%d,%p)\n", This, d3dts, lpmatrix);

  ENTER_GL();

  /* Lionel Ulmer says: */
  /* Using a trial and failure approach, I found that the order of
     Direct3D transformations that works best is :

     ScreenCoord = ProjectionMat * ViewMat * WorldMat * ObjectCoord

     As OpenGL uses only two matrices, I combined PROJECTION and VIEW into
     OpenGL's GL_PROJECTION matrix and the WORLD into GL_MODELVIEW.

     If anyone has a good explanation of the three different matrices in
     the SDK online documentation, feel free to point it to me. For example,
     which matrices transform lights ? In OpenGL only the PROJECTION matrix
     transform the lights, not the MODELVIEW. Using the matrix names, I
     supposed that PROJECTION and VIEW (all 'camera' related names) do
     transform lights, but WORLD do not. It may be wrong though... */

  /* After reading through both OpenGL and Direct3D documentations, I
     thought that D3D matrices were written in 'line major mode' transposed
     from OpenGL's 'column major mode'. But I found out that a simple memcpy
     works fine to transfer one matrix format to the other (it did not work
     when transposing)....

     So :
      1) are the documentations wrong
      2) does the matrix work even if they are not read correctly
      3) is Mesa's implementation of OpenGL not compliant regarding Matrix
         loading using glLoadMatrix ?

     Anyway, I always use 'conv_mat' to transfer the matrices from one format
     to the other so that if I ever find out that I need to transpose them, I
     will able to do it quickly, only by changing the macro conv_mat. */

  /* Ove Kåven says: */
  /* It seemed more natural to put the VIEW matrix into OpenGL's MODELVIEW
   * matrix, so I changed that... only the VIEW matrix should transform
   * lights, and OpenGL requires that to be in the MODELVIEW matrix anyway. */

  switch (d3dts) {
  case D3DTRANSFORMSTATE_WORLD: {
    conv_mat(lpmatrix, &odev->world_mat);
  } break;

  case D3DTRANSFORMSTATE_VIEW: {
    conv_mat(lpmatrix, &odev->view_mat);
    odev->view_generation++;
    odev->view_changed = TRUE;
  } break;

  case D3DTRANSFORMSTATE_PROJECTION: {
    conv_mat(lpmatrix, &odev->proj_mat);
#ifdef HAVE_OSMESA
    if (odev->osm_ctx) {
      FIXME("should probably invert Y transformation or something\n");
    }
#endif
  } break;

  default:
    break;
  }

  odev->transform_changed = TRUE;
  LEAVE_GL();
  return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetTransform(
    LPDIRECT3DDEVICE2 iface, D3DTRANSFORMSTATETYPE d3dts,
    LPD3DMATRIX lpmatrix
)
{
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  D3DDPRIVATE(This);

  TRACE("(%p)->(%d,%p)\n", This, d3dts, lpmatrix);

  ENTER_GL();

  switch (d3dts)
  {
  case D3DTRANSFORMSTATE_WORLD:
      conv_mat(&odev->world_mat, lpmatrix);
      break;

  case D3DTRANSFORMSTATE_VIEW:
      conv_mat(&odev->view_mat, lpmatrix);
      break;

  case D3DTRANSFORMSTATE_PROJECTION:
      conv_mat(&odev->proj_mat, lpmatrix);
      break;

  default:
      LEAVE_GL();
      FIXME("transformstatetype %d not supported\n", d3dts);
      return DDERR_INVALIDPARAMS;
  }

  LEAVE_GL();
  return DD_OK;
}

#define DRAW_PRIMITIVE(MAXVERT,INDEX)					\
  /* Puts GL in the correct lighting mode */				\
  if (odev->vt != d3dv) {						\
    if (odev->vt == D3DVT_TLVERTEX) {					\
      /* Need to put the correct transformation again */		\
      glMatrixMode(GL_MODELVIEW);					\
      glLoadMatrixf((const float *)&odev->view_mat); 			\
      glMultMatrixf((const float *)&odev->world_mat); 			\
      glMatrixMode(GL_PROJECTION);					\
      glLoadMatrixf((const float *)&odev->proj_mat);			\
    }									\
									\
    switch (d3dv) {							\
    case D3DVT_VERTEX:							\
      TRACE("Standard Vertex\n");					\
      glEnable(GL_LIGHTING);						\
      break;								\
									\
    case D3DVT_LVERTEX:							\
      TRACE("Lighted Vertex\n");					\
      glDisable(GL_LIGHTING);						\
      break;								\
									\
    case D3DVT_TLVERTEX: {						\
      GLdouble height, width, minZ, maxZ;				\
									\
      TRACE("Transformed - Lighted Vertex\n");				\
      /* First, disable lighting */					\
      glDisable(GL_LIGHTING);						\
									\
      /* Then do not put any transformation matrixes */			\
      glMatrixMode(GL_MODELVIEW);					\
      glLoadIdentity();							\
      glMatrixMode(GL_PROJECTION);					\
      glLoadIdentity();							\
									\
      if (This->current_viewport == NULL) {				\
	ERR("No current viewport !\n");					\
	/* Using standard values */					\
	height = 640.0;							\
	width = 480.0;							\
	minZ = -10.0;							\
	maxZ = 10.0;							\
      } else {								\
	if (This->current_viewport->use_vp2) {				\
	  height = (GLdouble) This->current_viewport->viewport.vp2.dwHeight;\
	  width  = (GLdouble) This->current_viewport->viewport.vp2.dwWidth;\
	  minZ   = (GLdouble) This->current_viewport->viewport.vp2.dvMinZ;\
	  maxZ   = (GLdouble) This->current_viewport->viewport.vp2.dvMaxZ;\
	} else {							\
	  height = (GLdouble) This->current_viewport->viewport.vp1.dwHeight;\
	  width  = (GLdouble) This->current_viewport->viewport.vp1.dwWidth;\
	  minZ   = (GLdouble) This->current_viewport->viewport.vp1.dvMinZ;\
	  maxZ   = (GLdouble) This->current_viewport->viewport.vp1.dvMaxZ;\
	}								\
      }									\
									\
      glOrtho(0.0, width, height, 0.0, -minZ, -maxZ);			\
    } break;								\
									\
    default:								\
      ERR("Unhandled vertex type\n");					\
      break;								\
    }									\
									\
    odev->vt = d3dv;							\
  }									\
									\
  switch (d3dp) {							\
  case D3DPT_POINTLIST:							\
    TRACE("Start POINTS\n");						\
    glBegin(GL_POINTS);							\
    break;								\
									\
  case D3DPT_LINELIST:							\
    TRACE("Start LINES\n");						\
    glBegin(GL_LINES);							\
    break;								\
									\
  case D3DPT_LINESTRIP:							\
    TRACE("Start LINE_STRIP\n");					\
    glBegin(GL_LINE_STRIP);						\
    break;								\
									\
  case D3DPT_TRIANGLELIST:						\
    TRACE("Start TRIANGLES\n");						\
    glBegin(GL_TRIANGLES);						\
    break;								\
									\
  case D3DPT_TRIANGLESTRIP:						\
    TRACE("Start TRIANGLE_STRIP\n");					\
    glBegin(GL_TRIANGLE_STRIP);						\
    break;								\
									\
  case D3DPT_TRIANGLEFAN:						\
    TRACE("Start TRIANGLE_FAN\n");					\
    glBegin(GL_TRIANGLE_FAN);						\
    break;								\
									\
  default:								\
    TRACE("Unhandled primitive\n");					\
    break;								\
  }									\
									\
  /* Draw the primitives */						\
  for (vx_index = 0; vx_index < MAXVERT; vx_index++) {			\
    switch (d3dv) {							\
    case D3DVT_VERTEX: {						\
      D3DVERTEX *vx = ((D3DVERTEX *) lpvertex) + INDEX;			\
									\
      glNormal3f(vx->u4.nx, vx->u5.ny, vx->u6.nz);			\
      glVertex3f(vx->u1.x, vx->u2.y, vx->u3.z);				\
      TRACE("   V: %f %f %f\n", vx->u1.x, vx->u2.y, vx->u3.z);		\
    } break;								\
									\
    case D3DVT_LVERTEX: {						\
      D3DLVERTEX *vx = ((D3DLVERTEX *) lpvertex) + INDEX;		\
      DWORD col = vx->u4.color;						\
									\
      glColor3f(((col >> 16) & 0xFF) / 255.0,				\
		((col >>  8) & 0xFF) / 255.0,				\
		((col >>  0) & 0xFF) / 255.0);				\
      glVertex3f(vx->u1.x, vx->u2.y, vx->u3.z);				\
      TRACE("  LV: %f %f %f (%02lx %02lx %02lx)\n",			\
	    vx->u1.x, vx->u2.y, vx->u3.z,				\
	    ((col >> 16) & 0xFF), ((col >>  8) & 0xFF), ((col >>  0) & 0xFF));\
    } break;								\
									\
    case D3DVT_TLVERTEX: {						\
      D3DTLVERTEX *vx = ((D3DTLVERTEX *) lpvertex) + INDEX;		\
      DWORD col = vx->u5.color;						\
									\
      glColor3f(((col >> 16) & 0xFF) / 255.0,				\
		((col >>  8) & 0xFF) / 255.0,				\
		((col >>  0) & 0xFF) / 255.0);				\
      glTexCoord2f(vx->u7.tu, vx->u8.tv);				\
      if (vx->u4.rhw < 0.01)						\
	glVertex3f(vx->u1.sx,						\
		   vx->u2.sy,						\
		   vx->u3.sz);						\
      else								\
	glVertex4f(vx->u1.sx / vx->u4.rhw,				\
		   vx->u2.sy / vx->u4.rhw,				\
		   vx->u3.sz / vx->u4.rhw,				\
		   1.0 / vx->u4.rhw);					\
      TRACE(" TLV: %f %f %f (%02lx %02lx %02lx) (%f %f) (%f)\n",	\
	    vx->u1.sx, vx->u2.sy, vx->u3.sz,				\
	    ((col >> 16) & 0xFF), ((col >>  8) & 0xFF), ((col >>  0) & 0xFF),\
	    vx->u7.tu, vx->u8.tv, vx->u4.rhw);				\
    } break;								\
									\
    default:								\
      FIXME("Unhandled vertex type\n");					\
      break;								\
    }									\
  }									\
									\
  glEnd();								\
  TRACE("End\n");


static HRESULT WINAPI MESA_IDirect3DDevice2Impl_DrawPrimitive(
    LPDIRECT3DDEVICE2 iface, D3DPRIMITIVETYPE d3dp, D3DVERTEXTYPE d3dv,
    LPVOID lpvertex, DWORD vertcount, DWORD dwFlags
) {
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  D3DDPRIVATE(This);
  int vx_index;

  TRACE("(%p)->(%d,%d,%p,%ld,%08lx): stub\n", This, d3dp, d3dv, lpvertex, vertcount, dwFlags);

  ENTER_GL();
  DRAW_PRIMITIVE(vertcount, vx_index);
  LEAVE_GL();

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice2Impl_DrawIndexedPrimitive(
    LPDIRECT3DDEVICE2 iface, D3DPRIMITIVETYPE d3dp, D3DVERTEXTYPE d3dv,
    LPVOID lpvertex, DWORD vertcount, LPWORD lpindexes, DWORD indexcount,
    DWORD dwFlags
) {
  ICOM_THIS(IDirect3DDevice2Impl,iface);
  D3DDPRIVATE(This);
  int vx_index;

  TRACE("(%p)->(%d,%d,%p,%ld,%p,%ld,%08lx): stub\n", This, d3dp, d3dv, lpvertex, vertcount, lpindexes, indexcount, dwFlags);

  ENTER_GL();
  DRAW_PRIMITIVE(indexcount, lpindexes[vx_index]);
  LEAVE_GL();

  return D3D_OK;
}

#undef DRAW_PRIMITIVE


/*******************************************************************************
 *				OpenGL-specific IDirect3DDevice2
 */

/*******************************************************************************
 *				OpenGL-specific VTable
 */
#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
# define XCAST(fun)	(typeof(OpenGL_d3dd2vt.fun))
#else
# define XCAST(fun)	(void*)
#endif

ICOM_VTABLE(IDirect3DDevice2) OpenGL_d3dd2vt =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  IDirect3DDevice2Impl_QueryInterface,
  IDirect3DDevice2Impl_AddRef,
  XCAST(Release)MESA_IDirect3DDeviceImpl_Release,
  /*** IDirect3DDevice2 methods ***/
  MESA_IDirect3DDevice2Impl_GetCaps,
  IDirect3DDevice2Impl_SwapTextureHandles,
  IDirect3DDevice2Impl_GetStats,
  IDirect3DDevice2Impl_AddViewport,
  IDirect3DDevice2Impl_DeleteViewport,
  IDirect3DDevice2Impl_NextViewport,
  XCAST(EnumTextureFormats)MESA_IDirect3DDeviceImpl_EnumTextureFormats,
  XCAST(BeginScene)MESA_IDirect3DDeviceImpl_BeginScene,
  XCAST(EndScene)MESA_IDirect3DDeviceImpl_EndScene,
  IDirect3DDevice2Impl_GetDirect3D,

  /*** DrawPrimitive API ***/
  IDirect3DDevice2Impl_SetCurrentViewport,
  IDirect3DDevice2Impl_GetCurrentViewport,

  IDirect3DDevice2Impl_SetRenderTarget,
  IDirect3DDevice2Impl_GetRenderTarget,

  IDirect3DDevice2Impl_Begin,
  IDirect3DDevice2Impl_BeginIndexed,
  IDirect3DDevice2Impl_Vertex,
  IDirect3DDevice2Impl_Index,
  IDirect3DDevice2Impl_End,

  IDirect3DDevice2Impl_GetRenderState,
  MESA_IDirect3DDevice2Impl_SetRenderState,
  IDirect3DDevice2Impl_GetLightState,
  MESA_IDirect3DDevice2Impl_SetLightState,
  MESA_IDirect3DDevice2Impl_SetTransform,
  IDirect3DDevice2Impl_GetTransform,
  IDirect3DDevice2Impl_MultiplyTransform,

  MESA_IDirect3DDevice2Impl_DrawPrimitive,
  MESA_IDirect3DDevice2Impl_DrawIndexedPrimitive,

  IDirect3DDevice2Impl_SetClipStatus,
  IDirect3DDevice2Impl_GetClipStatus,
};

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
#undef XCAST
#endif

/*******************************************************************************
 *				IDirect3DDevice7
 */

static ULONG WINAPI MESA_IDirect3DDevice7Impl_Release(LPDIRECT3DDEVICE iface)
{
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  FIXME("(%p)->() decrementing from %lu.\n", This, This->ref );

  if (!--(This->ref)) {
    ENTER_GL();
    GLX_Destroy((IDirect3DDeviceImpl*)This);
    LEAVE_GL();
    VirtualFree(odev->color_array, 0, MEM_DECOMMIT | MEM_RELEASE);
    HeapFree(GetProcessHeap(),0,This->private);
    This->private = NULL;
    IDirect3D_Release((LPDIRECT3D)This->d.d3d);
    HeapFree(GetProcessHeap(),0,odev->lights);
    HeapFree(GetProcessHeap(),0,This->lights);
    HeapFree(GetProcessHeap(),0,This);
    return 0;
  }
  return This->ref;
}

static HRESULT WINAPI MESA_IDirect3DDevice3Impl_EnumTextureFormats(
    LPDIRECT3DDEVICE3 iface,LPD3DENUMPIXELFORMATSCALLBACK lpd3dEnumPixelProc,LPVOID lpArg)
{
  ICOM_THIS(IDirect3DDeviceImpl,iface);
  TRACE("(%p)->(%p,%p): stub\n", This, lpd3dEnumPixelProc, lpArg);

  return enum_texture_format_OpenGL((LPD3DENUMTEXTUREFORMATSCALLBACK)lpd3dEnumPixelProc, lpArg, 1);
}

static inline void GL_UpdateLightPosition_NoMatrix(mesa_d3dd_private* odev,
						   DWORD i)
{
    const D3DLIGHT7 *lpLight = &(odev->lights[i].l);

    TRACE("updating %ld\n", i);

    glLightfv(GL_LIGHT0+i, GL_POSITION, odev->lights[i].pos);
    if (odev->lights[i].l.dltType == D3DLIGHT_SPOT)
    {
	TRACE(" SpotDir: %f %f %f\n", lpLight->dvDirection.u1.x,
	      lpLight->dvDirection.u2.y, lpLight->dvDirection.u3.z);
	glLightfv(GL_LIGHT0+i, GL_SPOT_DIRECTION, (float *)&(lpLight->dvDirection));
    }

    odev->lights[i].view_generation = odev->view_generation;
}

/* Update the light position without checking view_generation. */
static inline void GL_UpdateLightPosition(mesa_d3dd_private* odev, DWORD i)
{
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf((const float *)&odev->view_mat);
    GL_UpdateLightPosition_NoMatrix(odev, i);
    glPopMatrix();
}

static inline BOOL GL_LightNeedsUpdate(mesa_d3dd_private* odev, DWORD i)
{
    return (odev->lights[i].enabled
	    && odev->view_generation != odev->lights[i].view_generation);
}

/* Reload the OpenGL lights from the D3D lights.  */
static void MESA_DoUpdateLights(IDirect3DDevice7Impl *This)
{
    D3DDPRIVATE(This);
    int i;

    /* In D3D, light positions are specified in world coordinates, so
     * reload the modelview matrix with just the view matrix. */
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf((const float *)&odev->view_mat);

    for (i=0; i < odev->num_lights; i++)
    {
	if (GL_LightNeedsUpdate(odev, i))
	    GL_UpdateLightPosition_NoMatrix(odev, i);
    }

    /* restore world+view matrix */
    glPopMatrix();

    odev->view_changed = FALSE;
}

static inline void MESA_UpdateLights(IDirect3DDevice7Impl* This)
{
    D3DDPRIVATE(This);

    if (This->d.light && odev->view_changed)
	MESA_DoUpdateLights(This);
}

/* Apply the viewport parameters. */
static void GL_SetupViewport(IDirect3DDevice7Impl* This)
{
    D3DDPRIVATE(This);
    GLint gl_y;

    /* D3D uses MM_TEXT coordinates. GL has 0,0 in the bottom left,
     * with positive Y going up, and it wants the bottom left corner
     * of the viewport. */
    gl_y = odev->drawable_height
	- (This->viewport.dwY + This->viewport.dwHeight),

    glViewport(This->viewport.dwX, gl_y, This->viewport.dwWidth,
	       This->viewport.dwHeight);
    glScissor(This->viewport.dwX, gl_y, This->viewport.dwWidth,
	      This->viewport.dwHeight);

    glDepthRange(This->viewport.dvMinZ, This->viewport.dvMaxZ);
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_BeginScene(
    LPDIRECT3DDEVICE7 iface
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);

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

  if (This->d.in_scene) return D3DERR_SCENE_IN_SCENE;

  This->d.lock_device(This);

  This->d.in_scene = TRUE;

  GL_SetupViewport(This);

  LEAVE_GL();

  return DD_OK;
}

HRESULT WINAPI MESA_IDirect3DDevice7Impl_Clear(LPDIRECT3DDEVICE7 iface,
					       DWORD dwCount,LPD3DRECT lpRects,DWORD dwFlags,
					       D3DCOLOR dwColor,D3DVALUE dvZ,DWORD dwStencil)
{
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  GLboolean ztest = GL_FALSE;
  GLbitfield mask = 0;

  TRACE("(%p)->(%8ld,%p,%08lx,%08lx,%f,%ld)\n", This, dwCount, lpRects,
	dwFlags, dwColor, dvZ, dwStencil);

  /* For the moment, ignore the rectangles */

  if (This->d.in_scene) ENTER_GL();
  else This->d.lock_device(This);

  GL_SetupViewport(This);

  TRACE("%ld,%ld-%ld,%ld\n", This->viewport.dwX, This->viewport.dwY,
	This->viewport.dwWidth, This->viewport.dwHeight);

  if (dwFlags & D3DCLEAR_TARGET) {
    mask |= GL_COLOR_BUFFER_BIT;
    glClearColor(RGBA_GETRED(dwColor) / 255.0, RGBA_GETGREEN(dwColor) / 255.0, RGBA_GETBLUE(dwColor) / 255.0,
                 RGBA_GETALPHA(dwColor) / 255.0);
  }
  if (dwFlags & D3DCLEAR_STENCIL) {
    mask |= GL_STENCIL_BUFFER_BIT;
    glClearStencil(dwStencil);
  }
  if (dwFlags & D3DCLEAR_ZBUFFER) {
    mask |= GL_DEPTH_BUFFER_BIT;
    glClearDepth(dvZ);
    glGetBooleanv(GL_DEPTH_WRITEMASK, &ztest);
    glDepthMask(GL_TRUE); /* Enables Z writing to be sure to delete also the Z buffer */
  }

  glClear(mask);

  if (dwFlags & D3DCLEAR_ZBUFFER)
    glDepthMask(ztest);

  if (This->d.in_scene) LEAVE_GL();
  else This->d.unlock_device(This);

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetViewport(
    LPDIRECT3DDEVICE7 iface,LPD3DVIEWPORT7 vp
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  TRACE("(%p)->(%p)\n", This, vp);

  *vp = This->viewport;

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_SetViewport(
    LPDIRECT3DDEVICE7 iface,LPD3DVIEWPORT7 vp
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  TRACE("(%p)->(%p)\n", This, vp);
  TRACE("%ld,%ld-%ld,%ld %f--%f\n", vp->dwX, vp->dwY, vp->dwWidth,
	vp->dwHeight, vp->dvMinZ, vp->dvMaxZ);

  This->viewport = *vp;
  GL_SetupViewport(This);

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_SetMaterial(
    LPDIRECT3DDEVICE7 iface,LPD3DMATERIAL7 lpMat
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  TRACE("(%p)->(%p)\n", This, lpMat);

  ENTER_GL();

  odev->material = *lpMat;

  _dump_colorvalue("Diffuse", lpMat->u.diffuse);
  glMaterialfv(GL_FRONT_AND_BACK,
               GL_DIFFUSE,
               (float *) &(lpMat->u.diffuse));
  _dump_colorvalue("Ambient", lpMat->u1.ambient);
  glMaterialfv(GL_FRONT_AND_BACK,
               GL_AMBIENT,
               (float *) &(lpMat->u1.ambient));
  _dump_colorvalue("Specular", lpMat->u2.specular);
  if (odev->specular)
  {
      glMaterialfv(GL_FRONT_AND_BACK,
		   GL_SPECULAR,
		   (float *) &(lpMat->u2.specular));
  }
  _dump_colorvalue("Emissive", lpMat->u3.emissive);
  glMaterialfv(GL_FRONT_AND_BACK,
               GL_EMISSION,
               (float *) &(lpMat->u3.emissive));
  TRACE("Power : %f\n", lpMat->u4.power);
  glMaterialf(GL_FRONT_AND_BACK,
              GL_SHININESS,
              lpMat->u4.power);
  LEAVE_GL();

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetMaterial(
    LPDIRECT3DDEVICE7 iface, LPD3DMATERIAL7 lpMat
)
{
    ICOM_THIS(IDirect3DDevice7Impl, iface);
    D3DDPRIVATE(This);
    TRACE("(%p)->(%p)\n", This, lpMat);

    *lpMat = odev->material;

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_SetLight(
    LPDIRECT3DDEVICE7 iface,DWORD dwLightIndex,LPD3DLIGHT7 lpLight
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  struct mesa_light* light = odev->lights + dwLightIndex;
  GLenum gl = GL_LIGHT0 + dwLightIndex;

  TRACE("(%p)->(%ld,%p)\n", This, dwLightIndex, lpLight);

  if (dwLightIndex >= odev->num_lights) return D3D_OK;

  light->l = *lpLight;

  /* Initialise the light's position. */
  if (lpLight->dltType == D3DLIGHT_DIRECTIONAL)
  {
      light->pos[0] = -lpLight->dvDirection.u1.x;
      light->pos[1] = -lpLight->dvDirection.u2.y;
      light->pos[2] = -lpLight->dvDirection.u3.z;
      light->pos[3] = 0.0;
      TRACE("     Dir: %f %f %f %f\n",
	    light->pos[0], light->pos[1], light->pos[2], light->pos[3]);
  }
  else
  {
      light->pos[0] = lpLight->dvPosition.u1.x;
      light->pos[1] = lpLight->dvPosition.u2.y;
      light->pos[2] = lpLight->dvPosition.u3.z;
      light->pos[3] = 1.0;
      TRACE("     Pos: %f %f %f %f\n",
	    light->pos[0], light->pos[1], light->pos[2], light->pos[3]);
  }

  GL_UpdateLightPosition(odev, dwLightIndex);

  /* And set the light attributes. */
  TRACE("configuring GL light %lu type %d\n", dwLightIndex, lpLight->dltType);
  _dump_colorvalue("Diffuse", lpLight->dcvDiffuse);
  glLightfv(gl, GL_DIFFUSE, (float *)&(lpLight->dcvDiffuse));
  _dump_colorvalue("Specular", lpLight->dcvSpecular);
  glLightfv(gl, GL_SPECULAR, (float *)&(lpLight->dcvSpecular));
  _dump_colorvalue("Ambient", lpLight->dcvAmbient);
  glLightfv(gl, GL_AMBIENT, (float *)&(lpLight->dcvAmbient));

  /* I can't find a GL parameter for dvRange, and no exact match for dvFalloff */
  TRACE(" Attenuation: %f, %f, %f\n", lpLight->dvAttenuation0, lpLight->dvAttenuation1, lpLight->dvAttenuation2);
  glLightf(gl, GL_CONSTANT_ATTENUATION, lpLight->dvAttenuation0);
  glLightf(gl, GL_LINEAR_ATTENUATION, lpLight->dvAttenuation1);
  glLightf(gl, GL_QUADRATIC_ATTENUATION, lpLight->dvAttenuation2);
  /* I can't find an exact match for dvTheta either */
  /* how does GL_SPOT_EXPONENT relate to dvFalloff and dvTheta? */
  glLightf(gl, GL_SPOT_CUTOFF,
	   (lpLight->dltType != D3DLIGHT_SPOT) ? 180
	   : (lpLight->dvPhi * 90 / M_PI));

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetLight(
    LPDIRECT3DDEVICE7 iface,DWORD dwLightIndex,LPD3DLIGHT7 lpLight
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  TRACE("(%p)->(%ld,%p)\n", This, dwLightIndex, lpLight);

  if (dwLightIndex < odev->num_lights)
    *lpLight = odev->lights[dwLightIndex].l;
  return D3D_OK;
}

/* Fog is more complicated than it needs to be simply because D3D has two
 * types of fog, and if either is enabled we need to use that. If both are
 * enabled, we choose at default. */
static void MESA_UpdateFogEnable(IDirect3DDevice7Impl *This)
{
    D3DDPRIVATE(This);

    if (!odev->fog_enable)
    {
	glDisable(GL_FOG);
    }
    else
    {
	if (odev->vertex_fog_mode != D3DFOG_NONE
	    || odev->table_fog_mode != D3DFOG_NONE)
	{
	    glEnable(GL_FOG);
	}
    }
}

static void MESA_UpdateFogMode(IDirect3DDevice7Impl *This)
{
    static const GLint gl_fog_modes[4] = { 0, GL_EXP, GL_EXP2, GL_LINEAR };
    D3DDPRIVATE(This);
    D3DFOGMODE fog_mode;

    if (odev->table_fog_mode != D3DFOG_NONE)
    {
	fog_mode = odev->table_fog_mode;
	glHint(GL_FOG_HINT, GL_NICEST);
    }
    else
    {
	fog_mode = odev->vertex_fog_mode;
	glHint(GL_FOG_HINT, GL_FASTEST);
    }

    glFogi(GL_FOG_MODE, gl_fog_modes[fog_mode]);
}

/* This is intended for D3DRENDERSTATE_SRCBLEND, D3DRENDERSTATE_DESTBLEND,
 * but has two problems. First, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR are not
 * allowed source blend factors, and the corresponding destination-based
 * factors are not allowed destination blend factors, unless the
 * GL_NV_blend_square extension is present.
 * Second, we don't support the BOTH modes yet. */
static GLenum MESA_D3DBLEND_To_OpenGL(D3DBLEND b)
{
    static const GLenum gl_blends[] =
    { 0, GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA,
      GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA,
      GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA_SATURATE, 0, 0 };

    return gl_blends[b];
}

static D3DBLEND MESA_D3DBLEND_From_OpenGL(GLenum b)
{
    switch (b)
    {
    case GL_ZERO: return D3DBLEND_ZERO;
    case GL_ONE: return D3DBLEND_ONE;
    case GL_SRC_COLOR: return D3DBLEND_SRCCOLOR;
    case GL_ONE_MINUS_SRC_COLOR: return D3DBLEND_INVSRCCOLOR;
    case GL_SRC_ALPHA: return D3DBLEND_SRCALPHA;
    case GL_ONE_MINUS_SRC_ALPHA: return D3DBLEND_INVSRCALPHA;
    case GL_DST_ALPHA: return D3DBLEND_DESTALPHA;
    case GL_ONE_MINUS_DST_ALPHA: return D3DBLEND_INVDESTALPHA;
    case GL_DST_COLOR: return D3DBLEND_DESTCOLOR;
    case GL_ONE_MINUS_DST_COLOR: return D3DBLEND_INVDESTCOLOR;
    case GL_SRC_ALPHA_SATURATE: return D3DBLEND_SRCALPHASAT;
    default: assert(FALSE); return 0;
    }
}

/* This is good for D3DRENDERSTATE_ZFUNC -> glDepthFunc,
 * D3DRENDERSTATE_ALPHAFUNC -> glAlphaFunc,
 * D3DRENDERSTATE_STENCTILFUNC -> glStencilFunc. */
static GLenum MESA_D3DCMPFUNC_To_OpenGL(D3DCMPFUNC cmp)
{
    static const GLenum gl_func[] =
    { 0, GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL,
      GL_GEQUAL, GL_ALWAYS };

    return gl_func[cmp];
}

static D3DCMPFUNC MESA_D3DCMPFUNC_From_OpenGL(GLenum cmp)
{
    switch (cmp)
    {
    case GL_NEVER: return D3DCMP_NEVER;
    case GL_LESS: return D3DCMP_LESS;
    case GL_EQUAL: return D3DCMP_EQUAL;
    case GL_LEQUAL: return D3DCMP_LESSEQUAL;
    case GL_GREATER: return D3DCMP_GREATER;
    case GL_NOTEQUAL: return D3DCMP_NOTEQUAL;
    case GL_GEQUAL: return D3DCMP_GREATEREQUAL;
    case GL_ALWAYS: return D3DCMP_ALWAYS;
    default: assert(FALSE); return 0;
    }
}

static GLenum MESA_D3DSTENCILOP_To_OpenGL(D3DSTENCILOP op)
{
    static const GLenum gl_op[] =
    { 0, GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT,
      GL_INCR, GL_DECR };
    /* XXX The last two should be GL_INCRWRAP_EXT and GL_DECRWRAP_EXT if the
     * GL_EXT_stencil_wrap extension is supported. */

    return gl_op[op];
}

static D3DSTENCILOP MESA_D3DSTENCILOP_From_OpenGL(GLenum op)
{
    switch (op)
    {
    case GL_KEEP: return D3DSTENCILOP_KEEP;
    case GL_ZERO: return D3DSTENCILOP_ZERO;
    case GL_REPLACE: return D3DSTENCILOP_REPLACE;
    case GL_INCR: return D3DSTENCILOP_INCRSAT;
    case GL_DECR: return D3DSTENCILOP_DECRSAT;
    case GL_INVERT: return D3DSTENCILOP_INVERT;
#if 0
    case GL_INCRWRAP_EXT: return D3DSTENCILOP_INCR;
    case GL_DECRWRAP_EXT: return D3DSTENCILOP_DECR;
#endif
    default: assert(FALSE); return 0;
    }
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_SetRenderState(
    LPDIRECT3DDEVICE7 iface,D3DRENDERSTATETYPE dwRenderStateType,DWORD dwRenderState
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  float fRenderState = *(float *)&dwRenderState;
  D3DCOLOR cRenderState = *(D3DCOLOR *)&dwRenderState;

  TRACE("(%p)->(%d,%ld)\n", This, dwRenderStateType, dwRenderState);

  ENTER_GL();
  This->d.set_context(This);
  switch (dwRenderStateType) {

  case D3DRENDERSTATE_ZENABLE:
    if (dwRenderState) glEnable(GL_DEPTH_TEST);
    else glDisable(GL_DEPTH_TEST);
    break;

  case D3DRENDERSTATE_ZWRITEENABLE:
    glDepthMask(dwRenderState);
    break;

  case D3DRENDERSTATE_ZFUNC:
      glDepthFunc(MESA_D3DCMPFUNC_To_OpenGL(dwRenderState));
      break;

  case D3DRENDERSTATE_FILLMODE:
#ifndef WIREFRAME
    switch (dwRenderState) {
    case D3DFILL_POINT:     glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); break;
    case D3DFILL_WIREFRAME: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); break;
    case D3DFILL_SOLID:     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); break;
    default: FIXME("Unknown fill mode: %ld\n", dwRenderState);
    }
#endif
    break;
  case D3DRENDERSTATE_SHADEMODE:
    switch (dwRenderState) {
    case D3DSHADE_FLAT:    glShadeModel(GL_FLAT); break;
    case D3DSHADE_GOURAUD: glShadeModel(GL_SMOOTH); break;
    default: FIXME("Unknown shading model: %ld\n", dwRenderState);
    }
    break;
  case D3DRENDERSTATE_CULLMODE:
    switch (dwRenderState) {
    case D3DCULL_NONE: glDisable(GL_CULL_FACE); break;
    case D3DCULL_CW:   glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); break;
    case D3DCULL_CCW:  glEnable(GL_CULL_FACE); glCullFace(GL_BACK); break;
    default: FIXME("Unknown cull mode: %ld\n", dwRenderState);
    }
    break;
  case D3DRENDERSTATE_DITHERENABLE:
    if (dwRenderState) glEnable(GL_DITHER);
    else glDisable(GL_DITHER);
    break;
  case D3DRENDERSTATE_LIGHTING:
    This->d.light = dwRenderState;
    if (dwRenderState) glEnable(GL_LIGHTING);
    else glDisable(GL_LIGHTING);
    break;
  case D3DRENDERSTATE_AMBIENT:
    {
      float col[4];
      col[0] = RGBA_GETRED(cRenderState)   / 255.0;
      col[1] = RGBA_GETGREEN(cRenderState) / 255.0;
      col[2] = RGBA_GETBLUE(cRenderState)  / 255.0;
      col[3] = RGBA_GETALPHA(cRenderState) / 255.0;
      glLightModelfv(GL_LIGHT_MODEL_AMBIENT, col);
    }
    break;

  case D3DRENDERSTATE_SPECULARENABLE:
      odev->specular = dwRenderState;
      if (odev->specular)
	  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
		       (const float *)&odev->material.u2.specular);
      else
	  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, zero4f);
      break;

  case D3DRENDERSTATE_FOGENABLE:
      odev->fog_enable = dwRenderState;
      MESA_UpdateFogEnable(This);
      break;

  case D3DRENDERSTATE_FOGCOLOR:
  {
      GLfloat col[4];
      col[0] = RGBA_GETRED(cRenderState)   / 255.0;
      col[1] = RGBA_GETGREEN(cRenderState) / 255.0;
      col[2] = RGBA_GETBLUE(cRenderState)  / 255.0;
      col[3] = 0;
      glFogfv(GL_FOG_COLOR, col);
  }
  break;

  case D3DRENDERSTATE_FOGSTART:
      glFogf(GL_FOG_START, fRenderState);
      break;

  case D3DRENDERSTATE_FOGEND:
      glFogf(GL_FOG_END, fRenderState);
      break;

  case D3DRENDERSTATE_FOGDENSITY:
      /* XXX Only non-negative densities are accepted. */
      glFogf(GL_FOG_DENSITY, fRenderState);
      break;

  case D3DRENDERSTATE_FOGVERTEXMODE:
      if (odev->vertex_fog_mode != dwRenderState)
      {
	  odev->vertex_fog_mode = dwRenderState;
	  MESA_UpdateFogMode(This);
      }
      break;

  case D3DRENDERSTATE_FOGTABLEMODE:
      if (odev->table_fog_mode != dwRenderState)
      {
	  odev->table_fog_mode = dwRenderState;
	  MESA_UpdateFogMode(This);
      }
      break;

  case D3DRENDERSTATE_ALPHATESTENABLE:
      if (dwRenderState) glEnable(GL_ALPHA_TEST);
      else glDisable(GL_ALPHA_TEST);
      break;

  case D3DRENDERSTATE_SRCBLEND:
      odev->src_blend = MESA_D3DBLEND_To_OpenGL(dwRenderState);
      glBlendFunc(odev->src_blend, odev->dst_blend);
      break;

  case D3DRENDERSTATE_DESTBLEND:
      odev->dst_blend = MESA_D3DBLEND_To_OpenGL(dwRenderState);
      glBlendFunc(odev->src_blend, odev->dst_blend);
      break;

  case D3DRENDERSTATE_ALPHAREF:
      odev->alpha_ref = (dwRenderState & 0xFF) / 255.0;
      glAlphaFunc(odev->alpha_func, odev->alpha_ref);
      break;

  case D3DRENDERSTATE_ALPHAFUNC:
      odev->alpha_func = MESA_D3DCMPFUNC_To_OpenGL(dwRenderState);
      glAlphaFunc(odev->alpha_func, odev->alpha_ref);
      break;

  case D3DRENDERSTATE_ALPHABLENDENABLE:
      if (dwRenderState) glEnable(GL_BLEND);
      else glDisable(GL_BLEND);
      break;

  case D3DRENDERSTATE_STENCILENABLE:
      if (dwRenderState) glEnable(GL_STENCIL_TEST);
      else glDisable(GL_STENCIL_TEST);
      break;

  case D3DRENDERSTATE_STENCILFAIL:
      odev->stencil_fail_op = MESA_D3DSTENCILOP_To_OpenGL(dwRenderState);
      glStencilOp(odev->stencil_fail_op, odev->stencil_zfail_op,
		  odev->stencil_pass_op);
      break;

  case D3DRENDERSTATE_STENCILZFAIL:
      odev->stencil_zfail_op = MESA_D3DSTENCILOP_To_OpenGL(dwRenderState);
      glStencilOp(odev->stencil_fail_op, odev->stencil_zfail_op,
		  odev->stencil_pass_op);
      break;

  case D3DRENDERSTATE_STENCILPASS:
      odev->stencil_pass_op = MESA_D3DSTENCILOP_To_OpenGL(dwRenderState);
      glStencilOp(odev->stencil_fail_op, odev->stencil_zfail_op,
		  odev->stencil_pass_op);
      break;

  case D3DRENDERSTATE_STENCILFUNC:
      odev->stencil_func = MESA_D3DCMPFUNC_To_OpenGL(dwRenderState);
      glStencilFunc(odev->stencil_func, odev->stencil_ref,
		    odev->stencil_read_mask);
      break;

  case D3DRENDERSTATE_STENCILREF:
      odev->stencil_ref = dwRenderState;
      glStencilFunc(odev->stencil_func, odev->stencil_ref,
		    odev->stencil_read_mask);
      break;

  case D3DRENDERSTATE_STENCILMASK:
      odev->stencil_read_mask = dwRenderState;
      glStencilFunc(odev->stencil_func, odev->stencil_ref,
		    odev->stencil_read_mask);
      break;

  case D3DRENDERSTATE_STENCILWRITEMASK:
      glStencilMask(dwRenderState);
      break;

  case D3DRENDERSTATE_CLIPPING:
      if (MESA_UseExtension(odev, EXT_clip_volume_hint))
      {
          if (dwRenderState)
              glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_FASTEST);
          else
              glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_NICEST);
      }
      odev->clip_enable = dwRenderState;
      break;

  case D3DRENDERSTATE_COLORVERTEX:
      odev->color_vertex = dwRenderState;
      break;

  case D3DRENDERSTATE_DIFFUSEMATERIALSOURCE:
      odev->diffuse_source = dwRenderState;
      break;

  case D3DRENDERSTATE_SPECULARMATERIALSOURCE:
      odev->specular_source = dwRenderState;
      break;

  case D3DRENDERSTATE_AMBIENTMATERIALSOURCE:
      odev->ambient_source = dwRenderState;
      break;

  case D3DRENDERSTATE_EMISSIVEMATERIALSOURCE:
      odev->emissive_source = dwRenderState;
      break;

  case D3DRENDERSTATE_NORMALIZENORMALS:
      if (dwRenderState) glEnable(GL_NORMALIZE);
      else glDisable(GL_NORMALIZE);
      break;

  case D3DRENDERSTATE_LOCALVIEWER:
      glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, dwRenderState);
      break;

  default:
    FIXME("(%p)->(%d,%ld): not implemented\n", This, dwRenderStateType, dwRenderState);
  }
  LEAVE_GL();

  return D3D_OK;
}

HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetRenderState(
    LPDIRECT3DDEVICE7 iface, D3DRENDERSTATETYPE dwRenderStateType,
    LPDWORD lpdwRenderState
)
{
    ICOM_THIS(IDirect3DDevice7Impl,iface);
    D3DDPRIVATE(This);
    GLint tmp;

    TRACE("(%p)->(%d)\n", This, dwRenderStateType);

    ENTER_GL();
    This->d.set_context(This);

    switch (dwRenderStateType)
    {
    case D3DRENDERSTATE_ZENABLE:
	*lpdwRenderState = glIsEnabled(GL_DEPTH_TEST);
	break;

    case D3DRENDERSTATE_ZWRITEENABLE:
	glGetIntegerv(GL_DEPTH_WRITEMASK, (GLint *)lpdwRenderState);
	break;

    case D3DRENDERSTATE_ZFUNC:
	glGetIntegerv(GL_DEPTH_FUNC, &tmp);
	*lpdwRenderState = MESA_D3DCMPFUNC_From_OpenGL(tmp);
	break;

    case D3DRENDERSTATE_FILLMODE:
    {
	GLint modes[2];	/* front and back */
	glGetIntegerv(GL_POLYGON_MODE, modes);
	switch (modes[0])
	{
	case GL_POINT:
	    *lpdwRenderState = D3DFILL_POINT;
	    break;

	case GL_LINE:
	    *lpdwRenderState = D3DFILL_WIREFRAME;
	    break;

	case GL_FILL:
	    *lpdwRenderState = D3DFILL_SOLID;
	    break;
	}
    }
    break;

    case D3DRENDERSTATE_SHADEMODE:
    {
	GLint mode;
	glGetIntegerv(GL_SHADE_MODEL, &mode);
	if (mode == GL_SMOOTH) *lpdwRenderState = D3DSHADE_FLAT;
	else *lpdwRenderState = D3DSHADE_GOURAUD;
    }
    break;

    case D3DRENDERSTATE_CULLMODE:
    {
	if (!glIsEnabled(GL_CULL_FACE)) *lpdwRenderState = D3DCULL_NONE;
	else
	{
	    GLint mode;
	    glGetIntegerv(GL_CULL_FACE_MODE, &mode);
	    if (mode == GL_FRONT) *lpdwRenderState = D3DCULL_CW;
	    else *lpdwRenderState = D3DCULL_CCW;
	}
    }
    break;

    case D3DRENDERSTATE_DITHERENABLE:
	*lpdwRenderState = glIsEnabled(GL_DITHER);
	break;

    case D3DRENDERSTATE_LIGHTING:
	*lpdwRenderState = This->d.light;
	break;

    case D3DRENDERSTATE_AMBIENT:
    {
	GLfloat col[4];
	glGetFloatv(GL_LIGHT_MODEL_AMBIENT, col);
	*lpdwRenderState = D3DRGBA(col[0], col[1], col[2], col[3]);
    }
    break;

    case D3DRENDERSTATE_SPECULARENABLE:
	*lpdwRenderState = odev->specular;

    case D3DRENDERSTATE_FOGENABLE:
       *lpdwRenderState = odev->fog_enable;
       break;

    case D3DRENDERSTATE_FOGCOLOR:
    {
	GLfloat col[4];
	glGetFloatv(GL_FOG_COLOR, col);
	*lpdwRenderState = D3DRGBA(col[0], col[1], col[2], col[3]);
    }
    break;

    case D3DRENDERSTATE_FOGSTART:
	glGetFloatv(GL_FOG_START, (float *)lpdwRenderState);
	break;

    case D3DRENDERSTATE_FOGEND:
	glGetFloatv(GL_FOG_END, (float *)lpdwRenderState);
	break;

    case D3DRENDERSTATE_FOGDENSITY:
	glGetFloatv(GL_FOG_DENSITY, (float *)lpdwRenderState);
	break;

    case D3DRENDERSTATE_FOGVERTEXMODE:
	*lpdwRenderState = odev->vertex_fog_mode;
	break;

    case D3DRENDERSTATE_FOGTABLEMODE:
	*lpdwRenderState = odev->table_fog_mode;
	break;

    case D3DRENDERSTATE_ALPHATESTENABLE:
	*lpdwRenderState = glIsEnabled(GL_ALPHA_TEST);
	break;

    case D3DRENDERSTATE_SRCBLEND:
	*lpdwRenderState = MESA_D3DBLEND_From_OpenGL(odev->src_blend);
	break;

    case D3DRENDERSTATE_DESTBLEND:
	*lpdwRenderState = MESA_D3DBLEND_From_OpenGL(odev->dst_blend);
	break;

    case D3DRENDERSTATE_ALPHAREF:
    {
	GLfloat ref;
	glGetFloatv(GL_ALPHA_TEST_REF, &ref);
	*lpdwRenderState = ref * 255;
    }
    break;

    case D3DRENDERSTATE_ALPHAFUNC:
	*lpdwRenderState = MESA_D3DCMPFUNC_From_OpenGL(odev->alpha_func);
	break;

    case D3DRENDERSTATE_ALPHABLENDENABLE:
	*lpdwRenderState = glIsEnabled(GL_BLEND);
	break;

    case D3DRENDERSTATE_STENCILENABLE:
	*lpdwRenderState = glIsEnabled(GL_STENCIL_TEST);
	break;

    case D3DRENDERSTATE_STENCILFAIL:
	*lpdwRenderState = MESA_D3DSTENCILOP_From_OpenGL(odev->stencil_fail_op);
	break;

    case D3DRENDERSTATE_STENCILZFAIL:
	*lpdwRenderState = MESA_D3DSTENCILOP_From_OpenGL(odev->stencil_zfail_op);
	break;

    case D3DRENDERSTATE_STENCILPASS:
	*lpdwRenderState = MESA_D3DSTENCILOP_From_OpenGL(odev->stencil_pass_op);
	break;

    case D3DRENDERSTATE_STENCILFUNC:
	*lpdwRenderState = MESA_D3DCMPFUNC_From_OpenGL(odev->stencil_func);
	break;

    case D3DRENDERSTATE_STENCILREF:
	*lpdwRenderState = odev->stencil_ref;
	break;

    case D3DRENDERSTATE_STENCILMASK:
	*lpdwRenderState = odev->stencil_read_mask;
	break;

    case D3DRENDERSTATE_STENCILWRITEMASK:
	glGetIntegerv(GL_STENCIL_WRITEMASK, (GLint *)lpdwRenderState);
	break;

    case D3DRENDERSTATE_CLIPPING:
        *lpdwRenderState = odev->clip_enable;
        break;

    case D3DRENDERSTATE_NORMALIZENORMALS:
	*lpdwRenderState = glIsEnabled(GL_NORMALIZE);
	break;

    case D3DRENDERSTATE_LOCALVIEWER:
	glGetIntegerv(GL_LIGHT_MODEL_LOCAL_VIEWER, (GLint*)lpdwRenderState);
	break;

    default:
	FIXME("(%p)->(%d): not implemented\n", This, dwRenderStateType);
	break;
    }

    LEAVE_GL();
    return D3D_OK;
}

/***** ***** ***** ***** *****  DrawPrimitive API   ***** ***** ***** ***** *****/

static inline void GL_FVF2Stride(DWORD d3dvtVertexType, LPVOID lpvVertices, LPD3DDRAWPRIMITIVESTRIDEDDATA sd)
{
  int i;
  DWORD stride = 0;
  LPBYTE ptr = (LPBYTE)lpvVertices;
  int tex_count = D3DFVF_TEXCOUNT(d3dvtVertexType);

  memset(sd, 0, sizeof(*sd));
  if (d3dvtVertexType & D3DFVF_XYZ) {
    sd->position.lpvData = ptr + stride;
    stride += 3*sizeof(float);
  }
  if (d3dvtVertexType & D3DFVF_XYZRHW) {
    sd->position.lpvData = ptr + stride;
    stride += 4*sizeof(float);
  }
  if (d3dvtVertexType & D3DFVF_RESERVED1) {
    /* just compatibility with D3DLVERTEX */
    stride += sizeof(DWORD);
  }
  /* FIXME: blending weight */
  if (d3dvtVertexType & D3DFVF_NORMAL) {
    sd->normal.lpvData = ptr + stride;
    stride += 3*sizeof(float);
  }
  if (d3dvtVertexType & D3DFVF_DIFFUSE) {
    sd->diffuse.lpvData = ptr + stride;
    stride += sizeof(D3DCOLOR);
  }
  if (d3dvtVertexType & D3DFVF_SPECULAR) {
    sd->specular.lpvData = ptr + stride;
    stride += sizeof(D3DCOLOR);
  }

  if (d3dvtVertexType & D3DFVF_TEXCOUNT_MASK)
  {
    int num_coords;
    for (i = 0; i < tex_count; i++) {
      num_coords = D3DFVF_TEXCOORDSIZE(d3dvtVertexType,i);
      sd->textureCoords[i].lpvData = ptr + stride;
      stride += num_coords * sizeof(float);
    }
    /* initialize unused pointers/strides */
    for (i = tex_count; i < D3DDP_MAXTEXCOORD; i++) {
      sd->textureCoords[i].lpvData = NULL;
      sd->textureCoords[i].dwStride = 0;
    }
  }

  sd->position.dwStride = stride;
  sd->normal.dwStride = stride;
  sd->diffuse.dwStride = stride;
  sd->specular.dwStride = stride;
  for (i = 0; i < tex_count; i++)
      sd->textureCoords[i].dwStride = stride;
}

static void GL_SetupTNL(IDirect3DDevice7Impl* This, DWORD vt)
{
  D3DDPRIVATE(This);

  if (odev->transform_changed || odev->vt != vt)
  {
    if ((vt & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW) {
      GLdouble height, width, minZ, maxZ;

      TRACE("Transformed Vertices\n");

      /* Do not put any transformation matrixes */
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();

      height = This->viewport.dwHeight;
      width  = This->viewport.dwWidth;
      minZ   = This->viewport.dvMinZ;
      maxZ   = This->viewport.dvMaxZ;

      /* TODO: Use 0 and 1 for Znear, Zfar. */
      /* TODO: glDepthRange(0, 1); */
      glOrtho(0.0, width, height, 0.0, -minZ, -maxZ);
    }
    else {
      /* Need to put the correct transformation again */
      glMatrixMode(GL_MODELVIEW);
      glLoadMatrixf((const float *)&odev->view_mat);
      glMultMatrixf((const float *)&odev->world_mat);
      glMatrixMode(GL_PROJECTION);
      glLoadMatrixf((const float *)&odev->proj_mat);

      /* TODO: Reload glDepthRange. */
      /* TODO: vertex blending? */
    }
  }

  /* DX8: Only This->d.light should be used to distinguish prelit. */
  if (This->d.light && (vt & D3DFVF_NORMAL)) {
      TRACE("Unlit Vertices\n");
      glEnable(GL_LIGHTING);
      MESA_UpdateLights(This);
  } else {
      TRACE("Lit Vertices\n");
      if (This->d.light) glDisable(GL_LIGHTING);
  }

  odev->vt = vt;
  odev->transform_changed = FALSE;
}

/* This considers the D3DFVF_DIFFUSE and D3DFVF_SPECULAR bits in vt and
 * converts those arrays into GL colour format. It doesn't pass them to GL.
 *
 * The return value is a pointer to the colour arrays, which each colour
 * stored as 4 unsigned bytes. If both colours are requested then the
 * arrays are interleaved with diffuse first.
 */
static LPVOID GL_BuildColorArrays(mesa_d3dd_private* odev, DWORD vt,
				  DWORD num_vertices,
				  LPD3DDRAWPRIMITIVESTRIDEDDATA sd)
{
    LPBYTE out;
    DWORD which;

    which = vt & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR);
    if (!which) return odev->color_array; /* ??? */

    out = odev->color_array;

    if (which == (D3DFVF_DIFFUSE | D3DFVF_SPECULAR))
    {
	/* This loop is written under the assumption that the diffuse and
	 * specular data are interleaved. If not then the cache striding
	 * will probably be very bad. */

	LPBYTE diffuse_in = sd->diffuse.lpvData;
	DWORD diffuse_stride = sd->diffuse.dwStride;
	LPBYTE specular_in = sd->specular.lpvData;
	DWORD specular_stride = sd->specular.dwStride;

	DWORD i;

	assert(num_vertices * 8 < 64*1024);

	for (i=0; i < num_vertices; i++)
	{
	    out[0] = diffuse_in[2]; /* R */
	    out[1] = diffuse_in[1]; /* G */
	    out[2] = diffuse_in[0]; /* B */
	    out[3] = diffuse_in[3]; /* A */

	    out[4] = specular_in[2]; /* R */
	    out[5] = specular_in[1]; /* G */
	    out[6] = specular_in[0]; /* B */
	    out[7] = specular_in[3]; /* A */

	    diffuse_in += diffuse_stride;
	    specular_in += specular_stride;
	    out += 8;
	}
    }
    else
    {
	LPBYTE in;
	DWORD stride;
	DWORD i;

	assert(num_vertices * 4 < 64*1024);

	if (which == D3DFVF_DIFFUSE)
	{
	    in = sd->diffuse.lpvData;
	    stride = sd->diffuse.dwStride;

	}
	else
	{
	    in = sd->specular.lpvData;
	    stride = sd->specular.dwStride;
	}

	for (i=0; i < num_vertices; i++)
	{
	    out[0] = in[2]; /* R */
	    out[1] = in[1]; /* G */
	    out[2] = in[0]; /* B */
	    out[3] = in[3]; /* A */

	    in += stride;
	    out += 4;
	}
    }

    return odev->color_array;
}

static void GL_SetupColorArrays(mesa_d3dd_private* odev, DWORD vt,
				DWORD num_vertices,
				LPD3DDRAWPRIMITIVESTRIDEDDATA sd)
{
    LPBYTE colors;
    DWORD stride;

    if (!(vt & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))) return;

    colors = GL_BuildColorArrays(odev, vt, num_vertices, sd);

    /* Really GL_BuildColorArrays should be telling us. */
    if ((vt & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
	== (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
	stride = 8;
    else
	stride = 4;

    if (vt & D3DFVF_DIFFUSE)
    {
	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(4, GL_UNSIGNED_BYTE, stride, colors);

	colors += 4;
    }
    else
	glColor4fv(one4f);

    if (vt & D3DFVF_SPECULAR)
    {
	if (MESA_UseExtension(odev, EXT_secondary_color))
	{
	    glEnableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
	    odev->glSecondaryColorPointerEXT(3, GL_UNSIGNED_BYTE, stride,
					     colors);
	}
    }
    else
	odev->glSecondaryColor3ubEXT(0, 0, 0);
}

static BOOL GL_SetupArrays(IDirect3DDevice7Impl *This, DWORD vt,
			   DWORD num_vertices,
			   LPD3DDRAWPRIMITIVESTRIDEDDATA sd)
{
  D3DDPRIVATE(This);
  DWORD i;
  BOOL fast = TRUE;

  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  if (MESA_UseExtension(odev, EXT_secondary_color))
      glDisableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);

  for (i = 0; i < odev->max_gl_textures; i++)
  {
    odev->glClientActiveTextureARB(GL_TEXTURE0_ARB + i);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    odev->glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 0);
  }

  /* XXX If D3D supports stride 0 to say that the array is packed, then
   * we may need to detect XYZRHW and change that to stride 16 since we
   * want to skip rhw. */
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3, GL_FLOAT, sd->position.dwStride, sd->position.lpvData);

  if (This->d.light && (vt & D3DFVF_NORMAL)) {
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, sd->normal.dwStride, sd->normal.lpvData);

    /* We don't support unlit coloured vertices yet. */
    if (odev->color_vertex)
    {
	DWORD vertex_colors, material_colors;

	vertex_colors = (vt >> 6) & 3; /* pick out diffuse and specular */
	material_colors = odev->diffuse_source | odev->specular_source
	    | odev->ambient_source | odev->emissive_source;

	if ((vertex_colors & material_colors) != 0)
	    fast = FALSE;
    }
  }
  else if (vt & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
  {
      GL_SetupColorArrays(odev, vt, num_vertices, sd);
  }

  if (vt & D3DFVF_TEXCOUNT_MASK) {
      const DWORD num_tex_coords = min(D3DFVF_TEXCOUNT(vt),
				       odev->max_gl_textures);

      for (i=0; i < num_tex_coords; i++)
      {
	  int coord_idx = odev->texstage[i].coord_idx;

	  odev->glClientActiveTextureARB(GL_TEXTURE0_ARB + i);
	  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	  glTexCoordPointer(2, GL_FLOAT, sd->textureCoords[coord_idx].dwStride,
			    sd->textureCoords[coord_idx].lpvData);
      }
  }

  return fast;
}

static inline GLenum GL_GetPT(D3DPRIMITIVETYPE d3dp)
{
  switch (d3dp) {
  case D3DPT_POINTLIST:
    TRACE("Draw POINTS\n");
    return GL_POINTS;

  case D3DPT_LINELIST:
    TRACE("Draw LINES\n");
    return GL_LINES;

  case D3DPT_LINESTRIP:
    TRACE("Draw LINE_STRIP\n");
    return GL_LINE_STRIP;

  case D3DPT_TRIANGLELIST:
    TRACE("Draw TRIANGLES\n");
    return GL_TRIANGLES;

  case D3DPT_TRIANGLESTRIP:
    TRACE("Draw TRIANGLE_STRIP\n");
    return GL_TRIANGLE_STRIP;

  case D3DPT_TRIANGLEFAN:
    TRACE("Draw TRIANGLE_FAN\n");
    return GL_TRIANGLE_FAN;

  default:
    TRACE("Unhandled primitive\n");
    return GL_POINTS;
  }
}

typedef DWORD (*MESA_IndexFunc)(DWORD idx, LPVOID p);

#define STRIDE(data,idx) \
 ((LPVOID)((LPBYTE)(data).lpvData + (idx) * (data).dwStride))

static void
MESA_DrawUnlitVertices(IDirect3DDevice7Impl* This, GLenum gltype, DWORD vt,
		       DWORD count, LPD3DDRAWPRIMITIVESTRIDEDDATA sd,
		       MESA_IndexFunc index, LPVOID index_data)
{
    const D3DDP_PTRSTRIDE *diffuse = NULL;
    const D3DDP_PTRSTRIDE *specular = NULL;
    const D3DDP_PTRSTRIDE *ambient = NULL;
    const D3DDP_PTRSTRIDE *emissive = NULL;
    BOOL do_diffuse = FALSE;
    BOOL do_specular = FALSE;
    BOOL do_ambient = FALSE;
    BOOL do_emissive = FALSE;
    BOOL has_color[2];
    const D3DDP_PTRSTRIDE *color_stride[2];
    DWORD cnt;
    D3DDPRIVATE(This);

    assert(vt & D3DFVF_NORMAL);
    assert(vt & D3DFVF_XYZ);
    assert(glIsEnabled(GL_LIGHTING));

    has_color[0] = vt & D3DFVF_DIFFUSE;
    has_color[1] = vt & D3DFVF_SPECULAR;

    color_stride[0] = &sd->diffuse;
    color_stride[1] = &sd->specular;

    if (odev->color_vertex)
    {
	if (odev->diffuse_source != D3DMCS_MATERIAL)
	{
	    if (has_color[odev->diffuse_source - 1])
	    {
		do_diffuse = TRUE;
		diffuse = color_stride[odev->diffuse_source - 1];

		glEnable(GL_COLOR_MATERIAL);
	    }
	}

	if (odev->specular && odev->specular_source != D3DMCS_MATERIAL)
	{
	    if (has_color[odev->specular_source - 1])
	    {
		do_specular = TRUE;
		specular = color_stride[odev->specular_source - 1];
	    }
	}

	if (odev->ambient_source != D3DMCS_MATERIAL)
	{
	    if (has_color[odev->ambient_source - 1])
	    {
		do_ambient = TRUE;
		ambient = color_stride[odev->ambient_source - 1];
	    }
	}

	if (odev->emissive_source != D3DMCS_MATERIAL)
	{
	    if (has_color[odev->emissive_source - 1])
	    {
		do_emissive = TRUE;
		emissive = color_stride[odev->emissive_source - 1];
	    }
	}
    }

    glBegin(gltype);
    for (cnt = 0; cnt < count; cnt++)
    {
	DWORD cur = index(cnt, index_data);

	if (do_diffuse)
	{
	    DWORD col = *(LPDWORD)STRIDE(*diffuse, cur);

	    glColor4ub(RGBA_GETRED(col), RGBA_GETGREEN(col), RGBA_GETBLUE(col),
		       RGBA_GETALPHA(col));
	}

	if (do_specular)
	{
	    DWORD col = *(LPDWORD)STRIDE(*specular, cur);

	    GLfloat color[4] =
		{ RGBA_GETRED(col) / 255.0,
		  RGBA_GETGREEN(col) / 255.0,
		  RGBA_GETBLUE(col) / 255.0,
		  RGBA_GETALPHA(col) / 255.0
		};

	    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color);
	}

	if (do_ambient)
	{
	    DWORD col = *(LPDWORD)STRIDE(*ambient, cur);

	    GLfloat color[4] =
		{ RGBA_GETRED(col) / 255.0,
		  RGBA_GETGREEN(col) / 255.0,
		  RGBA_GETBLUE(col) / 255.0,
		  RGBA_GETALPHA(col) / 255.0
		};

	    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color);
	}

	if (do_emissive)
	{
	    DWORD col = *(LPDWORD)STRIDE(*emissive, cur);

	    GLfloat color[4] =
		{ RGBA_GETRED(col) / 255.0,
		  RGBA_GETGREEN(col) / 255.0,
		  RGBA_GETBLUE(col) / 255.0,
		  RGBA_GETALPHA(col) / 255.0
		};

	    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, color);
	}

	glArrayElement(cur);
    }

    glEnd();

    if (do_diffuse)
    {
	glColor4fv((float *)&(odev->material.u.diffuse));
	glDisable(GL_COLOR_MATERIAL);
    }

    if (do_specular) glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
				  (float *)&(odev->material.u2.specular));
    if (do_ambient) glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,
				 (float *)&(odev->material.u1.ambient));
    if (do_emissive) glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,
				 (float *)&(odev->material.u3.emissive));
}

static inline void
MESA_DrawVertices(IDirect3DDevice7Impl* This, GLenum gltype, DWORD vt,
		  DWORD count, LPD3DDRAWPRIMITIVESTRIDEDDATA sd,
		  MESA_IndexFunc index, LPVOID index_data)
{
    if (This->d.light && (vt & D3DFVF_NORMAL))
	MESA_DrawUnlitVertices(This, gltype, vt, count, sd, index, index_data);
    else assert(FALSE);
}

static DWORD MESA_DirectIndex(DWORD i, LPVOID p)
{
    return i;
}

static DWORD MESA_IndexedIndex(DWORD i, LPVOID p)
{
    return ((LPWORD)p)[i];
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DrawPrimitiveStrided(
    LPDIRECT3DDEVICE7 iface, D3DPRIMITIVETYPE d3dp, DWORD d3dvtVertexType,
    LPD3DDRAWPRIMITIVESTRIDEDDATA lpD3DDrawPrimStrideData, DWORD dwVertexCount, DWORD dwFlags
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);

  TRACE("(%p)->(%d,%ld,%p,%ld,%08lx)\n", This,
        d3dp, d3dvtVertexType, lpD3DDrawPrimStrideData, dwVertexCount, dwFlags);

  if (!This->d.in_scene) return D3DERR_SCENE_NOT_IN_SCENE;

  ENTER_GL();

  GL_SetupTNL(This, d3dvtVertexType);
  if (GL_SetupArrays(This, d3dvtVertexType, dwVertexCount,
		     lpD3DDrawPrimStrideData))
      glDrawArrays(GL_GetPT(d3dp), 0, dwVertexCount);
  else
      MESA_DrawVertices(This, GL_GetPT(d3dp), d3dvtVertexType, dwVertexCount,
			lpD3DDrawPrimStrideData, MESA_DirectIndex, NULL);

  LEAVE_GL();

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DrawIndexedPrimitiveStrided(
    LPDIRECT3DDEVICE7 iface, D3DPRIMITIVETYPE d3dp, DWORD d3dvtVertexType,
    LPD3DDRAWPRIMITIVESTRIDEDDATA lpD3DDrawPrimStrideData, DWORD dwVertexCount,
    LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);

  TRACE("(%p)->(%d,%ld,%p,%ld,%p,%ld,%08lx)\n", This,
        d3dp, d3dvtVertexType, lpD3DDrawPrimStrideData, dwVertexCount, lpwIndices, dwIndexCount, dwFlags);

  if (!This->d.in_scene) return D3DERR_SCENE_NOT_IN_SCENE;

  ENTER_GL();

  GL_SetupTNL(This, d3dvtVertexType);
  if (GL_SetupArrays(This, d3dvtVertexType, dwVertexCount,
		     lpD3DDrawPrimStrideData))
      glDrawElements(GL_GetPT(d3dp), dwIndexCount, GL_UNSIGNED_SHORT, lpwIndices);
  else
      MESA_DrawVertices(This, GL_GetPT(d3dp), d3dvtVertexType, dwIndexCount,
			lpD3DDrawPrimStrideData, MESA_IndexedIndex,
			lpwIndices);

  LEAVE_GL();

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DrawPrimitive(
    LPDIRECT3DDEVICE7 iface, D3DPRIMITIVETYPE d3dp, DWORD d3dvtVertexType,
    LPVOID lpvVertices, DWORD dwVertexCount, DWORD dwFlags
) {
  D3DDRAWPRIMITIVESTRIDEDDATA sd;
  TRACE("(%p)->(%d,%ld,%p,%ld,%08lx)\n", iface,
        d3dp, d3dvtVertexType, lpvVertices, dwVertexCount, dwFlags);
  GL_FVF2Stride(d3dvtVertexType, lpvVertices, &sd);
  return MESA_IDirect3DDevice7Impl_DrawPrimitiveStrided(iface, d3dp, d3dvtVertexType, &sd, dwVertexCount, dwFlags);
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DrawIndexedPrimitive(
    LPDIRECT3DDEVICE7 iface, D3DPRIMITIVETYPE d3dp, DWORD d3dvtVertexType,
    LPVOID lpvVertices, DWORD dwVertexCount, LPWORD lpwIndices, DWORD dwIndexCount,
    DWORD dwFlags
) {
  D3DDRAWPRIMITIVESTRIDEDDATA sd;
  TRACE("(%p)->(%d,%ld,%p,%ld,%p,%ld,%08lx)\n", iface,
        d3dp, d3dvtVertexType, lpvVertices, dwVertexCount, lpwIndices, dwIndexCount, dwFlags);
  GL_FVF2Stride(d3dvtVertexType, lpvVertices, &sd);
  return MESA_IDirect3DDevice7Impl_DrawIndexedPrimitiveStrided(iface, d3dp, d3dvtVertexType, &sd, dwVertexCount, lpwIndices, dwIndexCount, dwFlags);
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DrawPrimitiveVB(
    LPDIRECT3DDEVICE7 iface, D3DPRIMITIVETYPE d3dp, LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuffer,
    DWORD dwStartVertex, DWORD dwNumVertices, DWORD dwFlags
) {
  D3DDRAWPRIMITIVESTRIDEDDATA sd;
  IDirect3DVertexBufferImpl  *vb = (IDirect3DVertexBufferImpl *)lpd3dVertexBuffer;

  TRACE("(%p)->(%d,%ld,%p,%ld,%08lx)\n", iface,
        d3dp, vb->desc.dwFVF, (vb->vb_data)+(vb->vertex_size*dwStartVertex), dwNumVertices, dwFlags);
  GL_FVF2Stride(vb->desc.dwFVF, (vb->vb_data)+(vb->vertex_size*dwStartVertex), &sd);
  return MESA_IDirect3DDevice7Impl_DrawPrimitiveStrided(iface, d3dp, vb->desc.dwFVF, &sd, dwNumVertices, dwFlags);
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DrawIndexedPrimitiveVB(
    LPDIRECT3DDEVICE7 iface, D3DPRIMITIVETYPE d3dp, LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuffer,
    DWORD dwStartVertex, DWORD dwNumVertices, LPWORD lpwIndices, DWORD dwIndexCount,
    DWORD dwFlags
) {
  D3DDRAWPRIMITIVESTRIDEDDATA sd;
  IDirect3DVertexBufferImpl  *vb = (IDirect3DVertexBufferImpl *)lpd3dVertexBuffer;

  TRACE("(%p)->%p(%d,%ld,%p,%ld,%p,%ld,%08lx)\n", iface, lpd3dVertexBuffer,
        d3dp, vb->desc.dwFVF, (vb->vb_data)+(vb->vertex_size*dwStartVertex), dwNumVertices, lpwIndices, dwIndexCount, dwFlags);
  GL_FVF2Stride( vb->desc.dwFVF, (vb->vb_data)+(vb->vertex_size*dwStartVertex), &sd);
  return MESA_IDirect3DDevice7Impl_DrawIndexedPrimitiveStrided(iface, d3dp, vb->desc.dwFVF, &sd, dwNumVertices, lpwIndices, dwIndexCount, dwFlags);
}

/* Right now, this only supports the formats we enumerate. */
static BOOL MESA_PixelFormat_To_TextureFormat(const DDPIXELFORMAT *pfmt,
					      GLenum *fmt,
					      GLint *ifmt,
					      GLenum *type)
{
    static const DWORD unsupported = DDPF_BUMPDUDV | DDPF_BUMPLUMINANCE
	| DDPF_COMPRESSED | DDPF_FOURCC | DDPF_PALETTEINDEXED1
	| DDPF_PALETTEINDEXED2 | DDPF_PALETTEINDEXED4 | DDPF_PALETTEINDEXED8
	| DDPF_PALETTEINDEXEDTO8 | DDPF_RGBTOYUV | DDPF_STENCILBUFFER
	| DDPF_YUV | DDPF_ZBUFFER | DDPF_ZPIXELS;

    /* And ALPHAPREMULT might cause trouble. */

    if (pfmt->dwFlags & unsupported)
	return FALSE;

    TRACE("surface format %08lx, %ld\n", pfmt->dwFlags, pfmt->u1.dwRGBBitCount);
    TRACE("masks: %08lx, %08lx, %08lx, %08lx\n", pfmt->u2.dwRBitMask, pfmt->u3.dwGBitMask, pfmt->u4.dwBBitMask, pfmt->u5.dwRGBAlphaBitMask);

    if (pfmt->dwFlags & DDPF_RGB)
    {
	if (pfmt->dwFlags & (DDPF_LUMINANCE|DDPF_ALPHA))
	    return FALSE;

	if (pfmt->dwFlags & DDPF_ALPHAPIXELS)
	{
	    *ifmt = GL_RGBA;
	    *fmt = GL_BGRA;

	    if (pfmt->u1.dwRGBBitCount == 32)
	    {
		*type = GL_UNSIGNED_BYTE;
		return TRUE;
	    }
	    else if (pfmt->u1.dwRGBBitCount == 16)
	    {
		if (pfmt->u5.dwRGBAlphaBitMask == 0x8000)
		    *type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		else
		    *type = GL_UNSIGNED_SHORT_4_4_4_4_REV;

		return TRUE;
	    }
	    else
		return FALSE;
	}
	else
	{
	    *ifmt = GL_RGB;

	    if (pfmt->u1.dwRGBBitCount == 32)
	    {
		*fmt = GL_BGRA;
		*type = GL_UNSIGNED_BYTE;
		return TRUE;
	    }
	    else if (pfmt->u1.dwRGBBitCount == 16)
	    {
		if (pfmt->u2.dwRBitMask & 0x8000)
		{
		    *fmt = GL_RGB;
		    *type = GL_UNSIGNED_SHORT_5_6_5;
		}
		else
		{
		    *fmt = GL_BGRA;
		    *type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		}

		return TRUE;
	    }
	    else
		return FALSE;
	}
    }

    if (pfmt->dwFlags & DDPF_LUMINANCE)
    {
	if (pfmt->dwFlags & DDPF_ALPHAPIXELS)
	{
	    *fmt = GL_LUMINANCE_ALPHA;
	    *ifmt = GL_LUMINANCE_ALPHA;
	    *type = GL_UNSIGNED_BYTE;
	}
	else
	{
	    *fmt = GL_LUMINANCE;
	    *ifmt = GL_LUMINANCE;
	    *type = GL_UNSIGNED_BYTE;
	}

	return TRUE;
    }

    return FALSE;
}

static void MESA_release(LPVOID ctx, LPVOID data)
{
    IDirect3DDevice7Impl *This = ctx;
    IDirectDrawSurfaceImpl *texture = data;

    ENTER_GL();
    This->d.set_context(This);
    glDeleteTextures(1, (GLuint*)&TEXTURE_ID(texture));
    LEAVE_GL();
}

static void MESA_unlock(LPVOID ctx, LPVOID data, LPRECT pRect)
{
    IDirect3DDevice7Impl *This = ctx;
    IDirectDrawSurfaceImpl *texture = data;
    GLint oldtex;

    ENTER_GL();
    This->d.set_context(This);
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldtex);
    glBindTexture(GL_TEXTURE_2D, TEXTURE_ID(texture));
    if (pRect) {
      glTexSubImage2D(GL_TEXTURE_2D, DIBGLLEVEL(texture),
                      pRect->left, pRect->top,
                      pRect->right - pRect->left, pRect->bottom - pRect->top,
                      DIBGLFMT(texture), DIBGLTYPE(texture), texture->surface_desc.lpSurface);
    } else {
      glTexSubImage2D(GL_TEXTURE_2D, DIBGLLEVEL(texture), 0, 0,
                      texture->surface_desc.dwWidth, texture->surface_desc.dwHeight,
                      DIBGLFMT(texture), DIBGLTYPE(texture),
                      texture->surface_desc.lpSurface);
    }
    glBindTexture(GL_TEXTURE_2D, oldtex);
    LEAVE_GL();
}

static HRESULT MESA_PrepTexture(IDirect3DDevice7Impl *This,
				IDirectDrawSurfaceImpl *texture,
				struct mesa_texture_stage *texstage)
{
    static DDSCAPS2 mipmap_caps = { DDSCAPS_TEXTURE | DDSCAPS_MIPMAP };
    LPDDPIXELFORMAT pfmt = &(texture->surface_desc.u4.ddpfPixelFormat);
    GLint ifmt;
    GLenum fmt, type;
    IDirectDrawSurface7 *mipmap;
    int level;

    if (!MESA_PixelFormat_To_TextureFormat(pfmt, &fmt, &ifmt, &type))
    {
	FIXME("unhandled format %lx, %ld\n", pfmt->dwFlags,
	      pfmt->u1.dwRGBBitCount);
	return DDERR_INVALIDPARAMS;
    }

    glGenTextures(1, (GLuint*)&TEXTURE_ID(texture));

    glPushAttrib(GL_TEXTURE_BIT); /* save the current texture bindings */

    glBindTexture(GL_TEXTURE_2D, (GLuint)TEXTURE_ID(texture));

    texture->aux_ctx = This;
    texture->aux_data = texture;
    texture->aux_release = MESA_release;

    mipmap = ICOM_INTERFACE(texture, IDirectDrawSurface7);
    IDirectDrawSurface7_AddRef(mipmap);

    level = 0;

    while (1)
    {
	IDirectDrawSurface7 *next;
	IDirectDrawSurfaceImpl *mipmapI;

	mipmapI = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7,
			      mipmap);

	glTexImage2D(GL_TEXTURE_2D, level, ifmt,
		     mipmapI->surface_desc.dwWidth,
		     mipmapI->surface_desc.dwHeight,
		     0, fmt, type,
		     mipmapI->surface_desc.lpSurface);

	DIBGLFMT(mipmapI) = fmt;
	DIBGLTYPE(mipmapI) = type;
	DIBGLLEVEL(mipmapI) = level;
	mipmapI->aux_ctx = This;
	mipmapI->aux_data = texture;
	mipmapI->aux_unlock = MESA_unlock;

	level++;

	/* Get the next mipmap. */
	if (FAILED(IDirectDrawSurface7_GetAttachedSurface(mipmap, &mipmap_caps,
							  &next)))
	{
	    IDirectDrawSurface7_Release(mipmap);
	    break;
	}

	IDirectDrawSurface7_Release(mipmap);
	mipmap = next;
    }

    level--; /* level is the max (smallest) mipmap level */

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level);

    glPopAttrib();

    TRACE("created GL texture %ld\n", TEXTURE_ID(texture));

    return D3D_OK;
}

/* Assumes that dwStage is the GL active texture stage. */
static void MESA_UpdateMinFilter(IDirect3DDevice7Impl *This, DWORD dwStage)
{
    D3DDPRIVATE(This);

    /* This is indexed by [mipfilter][minfilter]. */
    /* XXX We just use linear for anisotropic. */
    static const GLenum filters[4][4] = {
	{0,	0,	0,	0},
	{0,	GL_NEAREST, GL_LINEAR, GL_LINEAR},
	{0, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST},
	{0, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR}
    };

    D3DTEXTUREMIPFILTER mipfilter = odev->texstage[dwStage].mipfilter;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		    filters[mipfilter][odev->texstage[dwStage].minfilter]);
}

static void MESA_UpdateMagFilter(IDirect3DDevice7Impl* This, DWORD dwStage)
{
    D3DDPRIVATE(This);
    GLenum filter;

    if (odev->texstage[dwStage].magfilter == D3DTFG_POINT)
	filter = GL_NEAREST;
    else
	filter = GL_LINEAR;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}

static void MESA_UpdateTextureAddress(IDirect3DDevice7Impl* This,
				      DWORD dwStage)
{
    D3DDPRIVATE(This);

    static const GLenum addresses[] =
	{ 0, GL_REPEAT, GL_REPEAT /*XXX*/, GL_CLAMP, GL_CLAMP };

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
		    addresses[odev->texstage[dwStage].u_addr]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
		    addresses[odev->texstage[dwStage].v_addr]);
}

/* The texture bound to a unit has just changed, so reload all the
 * glTexParameter settings.
 * We need to do this because in D3D the settings are per texture unit,
 * while in GL they are per texture object.
 */
static void MESA_UpdateTextureUnit(IDirect3DDevice7Impl *This, DWORD dwStage)
{
    D3DDPRIVATE(This);

    if (odev->texstage[dwStage].enabled)
	glEnable(GL_TEXTURE_2D);
    else
	glDisable(GL_TEXTURE_2D);

    MESA_UpdateMinFilter(This, dwStage);
    MESA_UpdateMagFilter(This, dwStage);
    MESA_UpdateTextureAddress(This, dwStage);
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_SetTexture(
    LPDIRECT3DDEVICE7 iface,DWORD dwStage,LPDIRECTDRAWSURFACE7 lpTexture
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  IDirectDrawSurfaceImpl *text = (IDirectDrawSurfaceImpl*)lpTexture;

  TRACE("(%p)->(%ld,%p)\n", This, dwStage, lpTexture);

  ENTER_GL();
  This->d.set_context(This);

  if (!lpTexture)
  {
      if (odev->texstage[dwStage].texture != NULL)
	  IDirectDrawSurface7_Release(odev->texstage[dwStage].texture);
      odev->texstage[dwStage].texture = NULL;

      if (dwStage < odev->max_gl_textures)
      {
	  odev->glActiveTextureARB(GL_TEXTURE0_ARB + dwStage);
	  /* Allow GL to release the old texture. */
	  glBindTexture(GL_TEXTURE_2D, 0);
	  glDisable(GL_TEXTURE_2D);
      }

      LEAVE_GL();
      return DD_OK;
  }

  if (!TEXTURE_ID(text))
  {
      HRESULT hr = MESA_PrepTexture(This, text, &odev->texstage[dwStage]);
      if (!SUCCEEDED(hr))
      {
	  LEAVE_GL();
	  return hr;
      }
  }

  if (odev->texstage[dwStage].texture != NULL)
      IDirectDrawSurface7_Release(odev->texstage[dwStage].texture);
  odev->texstage[dwStage].texture = lpTexture;
  IDirectDrawSurface7_AddRef(lpTexture);

  if (dwStage < odev->max_gl_textures)
  {
      odev->glActiveTextureARB(GL_TEXTURE0_ARB + dwStage);
      glBindTexture(GL_TEXTURE_2D, TEXTURE_ID(text));
      MESA_UpdateTextureUnit(This, dwStage);
  }

  LEAVE_GL();
  TRACE("used GL texture %ld\n", TEXTURE_ID(text));

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetTexture(
    LPDIRECT3DDEVICE7 iface, DWORD dwStage, LPDIRECTDRAWSURFACE7 *ppTexture
)
{
    ICOM_THIS(IDirect3DDevice7Impl,iface);
    D3DDPRIVATE(This);

    TRACE("%lu %p\n", dwStage, ppTexture);

    *ppTexture = odev->texstage[dwStage].texture;
    if (*ppTexture != NULL) IDirectDrawSurface7_AddRef(*ppTexture);

    return DD_OK;
}

/* This only uses GL_ARB_multitexture. */
static HRESULT
MESA_standard_texture_combiner(mesa_d3dd_private* odev, DWORD dwStage,
			       D3DTEXTURESTAGESTATETYPE type, DWORD dwState)
{
    struct mesa_texture_stage* texstage = odev->texstage + dwStage;
    BOOL real_stage = dwStage < odev->max_gl_textures;
    int i;

    switch (type) {

    case D3DTSS_COLOROP:
	texstage->colorop = dwState;
	texstage->enabled = dwState != D3DTOP_DISABLE;

	if (!real_stage) break;

	switch (dwState) {
	case D3DTOP_DISABLE:
	    texstage->enabled = FALSE;
	    /* In D3D, D3DTOP_DISABLE, ends the texture pipeline at the
	     * previous stage. In OpenGL, disabling one texture stage just
	     * turns it into a no-op, the rest still execute. */
	    for (i = dwStage; i < odev->max_gl_textures; i++)
	    {
		odev->glActiveTextureARB(GL_TEXTURE0_ARB + i);
		glDisable(GL_TEXTURE_2D);
	    }
	    return D3D_OK; /* avoid the enable code */

	case D3DTOP_SELECTARG1:
	    switch (texstage->colorarg1) {
	    case D3DTA_TEXTURE:
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		break;
	    default:
		FIXME("Unhandled SELECTARG1 source: %ld\n", texstage->colorarg1);
	    }
	    break;

	case D3DTOP_MODULATE:
	    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	    break;

	case D3DTOP_ADD:
	    if ((texstage->colorarg1 == D3DTA_TEXTURE) &&
		(texstage->colorarg2 == D3DTA_CURRENT) &&
		MESA_UseExtension(odev, EXT_texture_env_add)) {
		    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
		} else
		    FIXME("Unhandled ADD sources: %ld, %ld\n", texstage->colorarg1, texstage->colorarg2);
	    break;

	default:
	    FIXME("Unknown colorop mode: %ld\n", dwState);
	}

	/* Now enable this texture stage, and all subsequent texture stages
	 * disabled because of it. */
	for (i = dwStage; i < odev->max_gl_textures; i++)
	{
	    if (odev->texstage[i].colorop == D3DTOP_DISABLE) break;
	    if (odev->texstage[i].texture == NULL) continue;
	    odev->glActiveTextureARB(GL_TEXTURE0_ARB + i);
	    glEnable(GL_TEXTURE_2D);
	}

	break;

    default:
	break;
    }

    return D3D_OK;
}

static GLenum MESA_TextureSource(DWORD source)
{
    static const GLint sources[] = {
	GL_PRIMARY_COLOR_EXT, /* should be diffuse */
	GL_PREVIOUS_EXT, GL_TEXTURE, GL_CONSTANT_EXT,
	GL_PRIMARY_COLOR_EXT /* should be specular */
    };

    return sources[source & D3DTA_SELECTMASK];
}

/* This uses the GL_EXT_texture_env_combine extension for multitexturing.
 * We assume the following settings and never change them:
 * GL_OPERAND0_RGB_EXT, GL_OPERAND1_RGB_EXT = GL_SRC_COLOR
 * GL_OPERAND0_ALPHA_EXT, GL_OPERAND1_ALPHA_EXT = GL_SRC_ALPHA
 * GL_OPERAND2_RGB_EXT, GL_OPERAND2_ALPHA_EXT = GL_SRC_ALPHA
 */
 static HRESULT
MESA_env_combine_texture_combiner(mesa_d3dd_private* odev, DWORD dwStage,
				  D3DTEXTURESTAGESTATETYPE type, DWORD dwState)
{
    BOOL real_stage = dwStage < odev->max_gl_textures;
    struct mesa_texture_stage* texstage = odev->texstage + dwStage;
    int i;

    switch (type)
    {
    case D3DTSS_COLOROP:
	texstage->colorop = dwState;
	texstage->enabled = dwState != D3DTOP_DISABLE;

	if (!real_stage) break;

	switch (dwState)
	{
	case D3DTOP_DISABLE:
	    /* In D3D, D3DTOP_DISABLE, ends the texture pipeline at the
	     * previous stage. In OpenGL, disabling one texture stage just
	     * turns it into a no-op, the rest still execute. */
	    for (i = dwStage; i < odev->max_gl_textures; i++)
	    {
		odev->glActiveTextureARB(GL_TEXTURE0_ARB + i);
		glDisable(GL_TEXTURE_2D);
	    }
	    return D3D_OK; /* avoid the enable code */

	case D3DTOP_SELECTARG1:
	    /* S_rgb = A1_rgb */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    break;

	case D3DTOP_SELECTARG2:
	    /* S_rgb = A2_rgb */
	    /* We can only select SOURCE0, so put arg2 there. */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    break;

	case D3DTOP_MODULATE:
	    /* S_rgb = A1_rgb * A2_rgb */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		    MESA_TextureSource(texstage->colorarg2));
	    break;

	case D3DTOP_MODULATE2X:
	    /* S_rgb = (A1_rgb * A2_rgb) * 2 */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    break;

	case D3DTOP_MODULATE4X:
	    /* S_rgb = (A1_rgb * A2_rgb) * 4 */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 4);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    break;

	case D3DTOP_ADD:
	    /* S_rgb = (A1_rgb + A2_rgb) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	  break;

	case D3DTOP_ADDSIGNED:
	    /* S_rgb = (A1_rgb + A2_rgb - 0.5) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD_SIGNED_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    break;

	case D3DTOP_ADDSIGNED2X:
	    /* S_rgb = (A1_rgb + A2_rgb - 0.5) * 2 */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD_SIGNED_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    break;

	case D3DTOP_SUBTRACT:
	    /* S_rgb = A1_rgb - A2_rgb */
	    FIXME("subtract requested but unsupported\n");
	    break;

	case D3DTOP_ADDSMOOTH:
	    /* S_rgb = A1_rgb + A2_rgb(1-A1_rgb) */
	    FIXME("addsmooth requested but unsupported\n");
	    break;

	case D3DTOP_BLENDDIFFUSEALPHA:
	    /* S = A1_rgb * D_a + A2_rgb * (1-D_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT,
		      GL_PRIMARY_COLOR_EXT);
	    break;

	case D3DTOP_BLENDTEXTUREALPHA:
	    /* S = A1_rgb * T_a + A2_rgb * (1-T_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_TEXTURE);
	    break;

	case D3DTOP_BLENDFACTORALPHA:
	    /* S = A1_rgb * F_a + A2_rgb * (1-F_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_CONSTANT_EXT);
	    break;

	case D3DTOP_BLENDTEXTUREALPHAPM:
	    /* S = A1_rgb + A2_rgb(1-T_a) */
	    FIXME("premultiplied blend requested but unsupported\n");
	    break;

	case D3DTOP_BLENDCURRENTALPHA:
	    /* S = A1_rgb * C_a + A2_rgb * (1-C_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_PREVIOUS_EXT);
	    break;

	case D3DTOP_PREMODULATE:
	    /* "Blend with the next texture stage." */
	    FIXME("premodulate requested but unsupported\n");
	    break;

	case D3DTOP_MODULATEALPHA_ADDCOLOR:
	    /* S = A1_rgb + A1_a * A2_rgb */
	    FIXME("MAAC requested but unsupported\n");
	    break;

	case D3DTOP_MODULATECOLOR_ADDALPHA:
	    /* S = A1_rgb * A2_rgb + A1_a */
	    FIXME("MCAA requested but unsupported\n");
	    break;

	case D3DTOP_MODULATEINVALPHA_ADDCOLOR:
	    /* S = A1_rgb + (1-A1_a) * A2_rgb */
	    FIXME("MIAAC requested but unsupported\n");
	    break;

	case D3DTOP_MODULATEINVCOLOR_ADDALPHA:
	    /* S = (1-A1_rgb) * A2_rgb + A1_a */
	    FIXME("MICAA requested but unsupported\n");
	    break;

	case D3DTOP_BUMPENVMAP:
	case D3DTOP_BUMPENVMAPLUMINANCE:
	case D3DTOP_DOTPRODUCT3:
	    FIXME("bumpmapping requested but unsupported\n");
	    break;
	}

	/* Now enable this texture stage, and all subsequent texture stages
	 * disabled because of it. */
	for (i = dwStage; i < odev->max_gl_textures; i++)
	{
	    if (odev->texstage[i].colorop == D3DTOP_DISABLE) break;
	    if (odev->texstage[i].texture == NULL) continue;
	    odev->glActiveTextureARB(GL_TEXTURE0_ARB + i);
	    glEnable(GL_TEXTURE_2D);
	}

	break;

    case D3DTSS_COLORARG1:
	texstage->colorarg1 = dwState;
	if (!real_stage) break;

	if (texstage->colorop != D3DTOP_SELECTARG2)
	{
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg1));
	}
	break;

    case D3DTSS_COLORARG2:
	texstage->colorarg2 = dwState;
	if (!real_stage) break;

	if (texstage->colorop == D3DTOP_SELECTARG2)
	{
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT,
		      MESA_TextureSource(texstage->colorarg2));
	}

	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT,
		  MESA_TextureSource(texstage->colorarg2));
	break;

    case D3DTSS_ALPHAOP:
	texstage->alphaop = dwState;
	if (!real_stage) break;

	switch (dwState)
	{
	case D3DTOP_DISABLE:
	    /* Alpha can't be disabled when colour is enabled. */
	    break;

	case D3DTOP_SELECTARG1:
	    /* S_a = A1_a */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    break;

	case D3DTOP_SELECTARG2:
	    /* S_a = A2_a */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_MODULATE:
	    /* S_a = A1_a * A2_a */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_MODULATE2X:
	    /* S_a = (A1_a * A2_a) * 2 */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 2);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_MODULATE4X:
	    /* S_a = (A1_a * A2_a) * 4 */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 4);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_ADD:
	    /* S_a = (A1_a + A2_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_ADD);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_ADDSIGNED:
	    /* S_a = (A1_a + A2_a - 0.5) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_ADD_SIGNED_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_ADDSIGNED2X:
	    /* S_a = (A1_a + A2_a - 0.5) * 2 */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_ADD_SIGNED_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 2);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    break;

	case D3DTOP_SUBTRACT:
	    /* S_a = A1_a - A2_a */
	    /* uh oh! */
	    FIXME("alpha subtract requested but unsupported\n");
	    break;

	case D3DTOP_ADDSMOOTH:
	    /* S_a = A1_a + A1_a(1-A1_a) */
	    FIXME("alpha addsmooth requested but unsupported\n");
	    break;

	case D3DTOP_BLENDDIFFUSEALPHA:
	    /* S = A1_a * D_a + A2_a * (1-D_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT,
		      GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA_EXT,
		      GL_PRIMARY_COLOR_EXT);
	    break;

	case D3DTOP_BLENDTEXTUREALPHA:
	    /* S = A1_a * T_a + A2_a * (1-T_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT,
		      GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA_EXT, GL_TEXTURE);
	    break;

	case D3DTOP_BLENDFACTORALPHA:
	    /* S = A1_a * F_a + A2_a * (1-F_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT,
		      GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA_EXT, GL_CONSTANT_EXT);
	    break;

	case D3DTOP_BLENDTEXTUREALPHAPM:
	    /* S = A1_a + A2_a(1-T_a) */
	    FIXME("premultiplied blend requested but unsupported\n");
	    break;

	case D3DTOP_BLENDCURRENTALPHA:
	    /* S = A1_a * C_a + A2_a * (1-C_a) */
	    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT,
		      GL_INTERPOLATE_EXT);
	    glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA_EXT, GL_PREVIOUS_EXT);
	    break;

	case D3DTOP_PREMODULATE:
	    /* "Blend with the next texture stage." */
	    break;

	case D3DTOP_MODULATEALPHA_ADDCOLOR:
	case D3DTOP_MODULATECOLOR_ADDALPHA:
	case D3DTOP_MODULATEINVALPHA_ADDCOLOR:
	case D3DTOP_MODULATEINVCOLOR_ADDALPHA:
	case D3DTOP_BUMPENVMAP:
	case D3DTOP_BUMPENVMAPLUMINANCE:
	    /* COLOROP only */
	    WARN("app requested colorop only mode for alpha\n");
	    break;

	case D3DTOP_DOTPRODUCT3:
	    FIXME("dot3 alpha requested but unsupported\n");
	    break;
	}
	break;

    case D3DTSS_ALPHAARG1:
	texstage->alphaarg1 = dwState;
	if (!real_stage) break;

	if (texstage->alphaop != D3DTOP_SELECTARG2)
	{
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg1));
	}
	break;

    case D3DTSS_ALPHAARG2:
	texstage->colorarg2 = dwState;
	if (!real_stage) break;

	if (texstage->alphaop == D3DTOP_SELECTARG2)
	{
	    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT,
		      MESA_TextureSource(texstage->alphaarg2));
	}

	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT,
		  MESA_TextureSource(texstage->alphaarg2));
	break;

    default:
	assert(FALSE);
    }

    return D3D_OK;
}



static HRESULT WINAPI MESA_IDirect3DDevice7Impl_SetTextureStageState(
    LPDIRECT3DDEVICE7 iface,DWORD dwStage,D3DTEXTURESTAGESTATETYPE d3dTexStageStateType,DWORD dwState
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);
  struct mesa_texture_stage* texstage = odev->texstage + dwStage;
  BOOL real_stage;
  HRESULT hr = D3D_OK;

  TRACE("(%p)->(%ld,%d,%ld)\n", This, dwStage, d3dTexStageStateType, dwState);

  ENTER_GL();
  This->d.set_context(This);

  real_stage = dwStage < odev->max_gl_textures;

  if (real_stage) odev->glActiveTextureARB(GL_TEXTURE0_ARB + dwStage);

  switch (d3dTexStageStateType) {

  case D3DTSS_COLOROP:
  case D3DTSS_COLORARG1:
  case D3DTSS_COLORARG2:
  case D3DTSS_ALPHAOP:
  case D3DTSS_ALPHAARG1:
  case D3DTSS_ALPHAARG2:
      hr = odev->texture_combiner(odev, dwStage, d3dTexStageStateType,
				  dwState);
      break;

  case D3DTSS_MAGFILTER:
      odev->texstage[dwStage].magfilter = dwState;
      if (real_stage) MESA_UpdateMagFilter(This, dwStage);
      break;

  case D3DTSS_MINFILTER:
      odev->texstage[dwStage].minfilter = dwState;
      if (real_stage) MESA_UpdateMinFilter(This, dwStage);
      break;

  case D3DTSS_MIPFILTER:
      odev->texstage[dwStage].mipfilter = dwState;
      if (real_stage) MESA_UpdateMinFilter(This, dwStage);
      break;

  case D3DTSS_TEXCOORDINDEX:
#if 0
      if (dwState & (D3DTSS_TCI_CAMERASPACENORMAL
		     | D3DTSS_TCI_CAMERASPACEPOSITION
		     | D3DDSS_TCI_CAMERASPACEREFLECTIONVECTOR))
      {
	  FIXME("TEXCOORDINDEX (%d) coordinate generation %08lx unsupported\n",
		dwStage, dwState);
	  dwStage &= ~(D3DTSS_TCI_CAMERASPACENORMAL
		       | D3DTSS_TCI_CAMERASPACEPOSITION
		       | D3DDSS_TCI_CAMERASPACEREFLECTIONVECTOR);
      }
#endif

      odev->texstage[dwStage].coord_idx = dwState;
      break;

  case D3DTSS_ADDRESS:
      odev->texstage[dwStage].u_addr = dwState;
      odev->texstage[dwStage].v_addr = dwState;
      if (real_stage) MESA_UpdateTextureAddress(This, dwStage);
      break;

  case D3DTSS_ADDRESSU:
      odev->texstage[dwStage].u_addr = dwState;
      if (real_stage) MESA_UpdateTextureAddress(This, dwStage);
      break;

  case D3DTSS_ADDRESSV:
      odev->texstage[dwStage].v_addr = dwState;
      if (real_stage) MESA_UpdateTextureAddress(This, dwStage);
      break;

  case D3DTSS_MIPMAPLODBIAS:
      texstage->lodbias = *(float *)&dwState;
      if (real_stage && MESA_UseExtension(odev, EXT_texture_lod_bias))
	  glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT,
		    texstage->lodbias);
      break;

  case D3DTSS_MAXMIPLEVEL:
      texstage->maxmiplevel = dwState;
      break;

  default:
    FIXME("(%p)->(%ld,%d,%ld): not implemented\n", This, dwStage, d3dTexStageStateType, dwState);
  }
  LEAVE_GL();

  return hr;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetTextureStageState(
    LPDIRECT3DDEVICE7 iface,DWORD dwStage,D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, LPDWORD lpdwValue
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);

  const struct mesa_texture_stage* texstage = odev->texstage + dwStage;

  TRACE("(%p)->(%ld,%d,%p)\n", This, dwStage, d3dTexStageStateType, lpdwValue);

  switch (d3dTexStageStateType) {

  case D3DTSS_COLOROP:
    *lpdwValue = odev->texstage[dwStage].colorop;
    break;

  case D3DTSS_COLORARG1:
      *lpdwValue = texstage->colorarg1;
      break;

  case D3DTSS_COLORARG2:
      *lpdwValue = texstage->colorarg2;
      break;

  case D3DTSS_ALPHAOP:
      *lpdwValue = texstage->alphaop;
      break;

  case D3DTSS_ALPHAARG1:
      *lpdwValue = texstage->alphaarg1;
      break;

  case D3DTSS_ALPHAARG2:
      *lpdwValue = texstage->alphaarg2;
      break;

  case D3DTSS_MAGFILTER:
    *lpdwValue = odev->texstage[dwStage].magfilter;
    break;

  case D3DTSS_MINFILTER:
    *lpdwValue = odev->texstage[dwStage].minfilter;
    break;

  case D3DTSS_MIPFILTER:
    *lpdwValue = odev->texstage[dwStage].mipfilter;
    break;

  case D3DTSS_TEXCOORDINDEX:
      *lpdwValue = texstage->coord_idx;
      break;

  case D3DTSS_ADDRESS:
      /* Just return the u address mode, since it's unclear what we should
       * do if there are different address modes on u and v */
      *lpdwValue = texstage->u_addr;
      break;

  case D3DTSS_ADDRESSU:
      *lpdwValue = texstage->u_addr;
      break;

  case D3DTSS_ADDRESSV:
      *lpdwValue = texstage->v_addr;
      break;

  default:
    FIXME("(%p)->(%ld,%d,%p): not implemented\n", This, dwStage, d3dTexStageStateType, lpdwValue);
    *lpdwValue = 0;
  }

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_LightEnable(
    LPDIRECT3DDEVICE7 iface,DWORD dwLightIndex,BOOL bEnable
) {
  ICOM_THIS(IDirect3DDevice7Impl,iface);
  D3DDPRIVATE(This);

  TRACE("(%p)->(%ld,%d)\n", This, dwLightIndex, bEnable);

  ENTER_GL();
  This->d.set_context(This);

  if (dwLightIndex < odev->num_lights)
  {
      odev->lights[dwLightIndex].enabled = bEnable;

      if (bEnable)
      {
	  /* Catch up on any view updates we missed while disabled. */
	  if (GL_LightNeedsUpdate(odev, dwLightIndex))
	      GL_UpdateLightPosition(odev, dwLightIndex);

	  glEnable(GL_LIGHT0 + dwLightIndex);
      }
      else glDisable(GL_LIGHT0 + dwLightIndex);
  }

  LEAVE_GL();
  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetCaps(
    LPDIRECT3DDEVICE7 iface, LPD3DDEVICEDESC7 lpCaps
)
{
    TRACE("\n");

    fill_opengl_caps7(lpCaps);

    return DD_OK;
}

/* Compute *pLeftMatrix *= *pRightMatrix. */
static inline void MESA_MatrixMultiply(LPD3DMATRIX pLeftMatrix,
				       const D3DMATRIX *pRightMatrix)
{
    int i, j, k;

    union MatrixArray {
	D3DMATRIX mat;
	GLfloat arr[4][4];
    };

    union MatrixArray tmp;
    union MatrixArray *pLeft = (union MatrixArray *)pLeftMatrix;
    const union MatrixArray *pRight = (const union MatrixArray *)pRightMatrix;

    for (i=0; i < 4; i++)
    {
	for (j=0; j < 4; j++)
	{
	    float sum = 0;
	    for (k=0; k < 4; k++)
		sum += pLeft->arr[j][k] * pRight->arr[k][i];
	    tmp.arr[i][j] = sum;
	}
    }

    *pLeftMatrix = tmp.mat;
}

/* Maybe this should be generic, then call out to a reload matricies hook. */
static HRESULT WINAPI MESA_IDirect3DDevice7Impl_MultiplyTransform(
    LPDIRECT3DDEVICE7 iface, D3DTRANSFORMSTATETYPE type,
    LPD3DMATRIX pRightMatrix
    )
{
    ICOM_THIS(IDirect3DDevice7Impl,iface);
    D3DDPRIVATE(This);

    LPD3DMATRIX pLeftMatrix;

    TRACE("\n");

    ENTER_GL();
    This->d.set_context(This);

    switch (type)
    {
    case D3DTRANSFORMSTATE_WORLD:
	pLeftMatrix = &odev->world_mat;
	break;

    case D3DTRANSFORMSTATE_VIEW:
	pLeftMatrix = &odev->view_mat;
	odev->view_generation++;
	odev->view_changed = TRUE;
	break;

    case D3DTRANSFORMSTATE_PROJECTION:
	pLeftMatrix = &odev->proj_mat;
	break;

    default:
	FIXME("unsupported transform %d\n", type);
	LEAVE_GL();
	return DDERR_INVALIDPARAMS;
    }

    MESA_MatrixMultiply(pLeftMatrix, pRightMatrix);

    odev->transform_changed = TRUE;

    LEAVE_GL();

    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_PreLoad(
    LPDIRECT3DDEVICE7 iface, LPDIRECTDRAWSURFACE7 lpTexture
)
{
    ICOM_THIS(IDirect3DDevice7Impl,iface);
    HRESULT hr = DD_OK;
    IDirectDrawSurfaceImpl *text = (IDirectDrawSurfaceImpl*)lpTexture;

    TRACE("(%p)->(%p)\n", This, lpTexture);

    /* we may not have much control over GL preloading,
     * but at least make sure the texture is prepared */
    if (!TEXTURE_ID(text))
    {
	ENTER_GL();
	This->d.set_context(This);
	hr = MESA_PrepTexture(This, text, NULL);
	LEAVE_GL();
    }

    return hr;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_CreateStateBlock(
    LPDIRECT3DDEVICE7 iface, D3DSTATEBLOCKTYPE d3dsbType, LPDWORD lpdwBlockHandle
)
{
    *lpdwBlockHandle = 0;
    FIXME("stub\n");
    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_ApplyStateBlock(
    LPDIRECT3DDEVICE7 iface, DWORD lpdwBlockHandle
)
{
    FIXME("stub\n");
    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_CaptureStateBlock(
    LPDIRECT3DDEVICE7 iface, DWORD lpdwBlockHandle
)
{
    FIXME("stub\n");
    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_DeleteStateBlock(
    LPDIRECT3DDEVICE7 iface, DWORD lpdwBlockHandle
)
{
    FIXME("stub\n");
    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_Load(
    LPDIRECT3DDEVICE7 iface, LPDIRECTDRAWSURFACE7 lpDestTex,
    LPPOINT lpDestPoint, LPDIRECTDRAWSURFACE7 lpSrcTex,
    LPRECT lprcSrcRect, DWORD dwFlags
)
{
    ICOM_THIS(IDirect3DDevice7Impl,iface);

    TRACE("(%p)->(%p,%p,%p,%p,%08lx)\n",This,lpDestTex,lpDestPoint,lpSrcTex,lprcSrcRect,dwFlags);
    if (dwFlags) {
      FIXME("cubic maps not implemented\n");
      return DDERR_INVALIDPARAMS;
    }
    IDirectDrawSurface7_BltFast(lpDestTex,
	lpDestPoint?lpDestPoint->x:0,
	lpDestPoint?lpDestPoint->y:0,
	lpSrcTex, lprcSrcRect,
	DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_ValidateDevice(
    LPDIRECT3DDEVICE7 iface, LPDWORD lpdwPasses
)
{
	*lpdwPasses = 1;
    FIXME("stub\n");
    return DD_OK;
}

static HRESULT WINAPI MESA_IDirect3DDevice7Impl_GetInfo(
    LPDIRECT3DDEVICE7 iface, DWORD DevInfoID, LPVOID pDevInfoStruct, DWORD DevInfoStructSize
)
{
    /* documented to returns S_FALSE on retail builds of DirectX */
    TRACE("(%ld): stub\n", DevInfoID);
    return S_FALSE;
}

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
# define XCAST(fun)	(typeof(OpenGL_d3dd7vt.fun))
#else
# define XCAST(fun)	(void*)
#endif

ICOM_VTABLE(IDirect3DDevice7) OpenGL_d3dd7vt =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  XCAST(QueryInterface)IDirect3DDeviceImpl_QueryInterface,
  XCAST(AddRef)IDirect3DDeviceImpl_AddRef,
  XCAST(Release)MESA_IDirect3DDevice7Impl_Release,
  MESA_IDirect3DDevice7Impl_GetCaps,
  XCAST(EnumTextureFormats)MESA_IDirect3DDevice3Impl_EnumTextureFormats,
  MESA_IDirect3DDevice7Impl_BeginScene,
  XCAST(EndScene)MESA_IDirect3DDeviceImpl_EndScene,
  XCAST(GetDirect3D)IDirect3DDeviceImpl_GetDirect3D,
  XCAST(SetRenderTarget)IDirect3DDevice2Impl_SetRenderTarget,
  XCAST(GetRenderTarget)IDirect3DDevice2Impl_GetRenderTarget,
  MESA_IDirect3DDevice7Impl_Clear,
  XCAST(SetTransform)MESA_IDirect3DDevice2Impl_SetTransform,
  XCAST(GetTransform)MESA_IDirect3DDevice7Impl_GetTransform,
  MESA_IDirect3DDevice7Impl_SetViewport,
  MESA_IDirect3DDevice7Impl_MultiplyTransform,
  MESA_IDirect3DDevice7Impl_GetViewport,
  MESA_IDirect3DDevice7Impl_SetMaterial,
  MESA_IDirect3DDevice7Impl_GetMaterial,
  MESA_IDirect3DDevice7Impl_SetLight,
  MESA_IDirect3DDevice7Impl_GetLight,
  MESA_IDirect3DDevice7Impl_SetRenderState,
  MESA_IDirect3DDevice7Impl_GetRenderState,
  0xdead0001, /* BeginStateBlock */
  0xdead0002, /* EndStateBlock */
  MESA_IDirect3DDevice7Impl_PreLoad,
  MESA_IDirect3DDevice7Impl_DrawPrimitive,
  MESA_IDirect3DDevice7Impl_DrawIndexedPrimitive,
  XCAST(SetClipStatus)IDirect3DDevice2Impl_SetClipStatus,
  XCAST(GetClipStatus)IDirect3DDevice2Impl_GetClipStatus,
  MESA_IDirect3DDevice7Impl_DrawPrimitiveStrided,
  MESA_IDirect3DDevice7Impl_DrawIndexedPrimitiveStrided,
  MESA_IDirect3DDevice7Impl_DrawPrimitiveVB,
  MESA_IDirect3DDevice7Impl_DrawIndexedPrimitiveVB,
  0xdead0003, /* ComputeSphereVisibility */
  MESA_IDirect3DDevice7Impl_GetTexture,
  MESA_IDirect3DDevice7Impl_SetTexture,
  MESA_IDirect3DDevice7Impl_GetTextureStageState,
  MESA_IDirect3DDevice7Impl_SetTextureStageState,
  MESA_IDirect3DDevice7Impl_ValidateDevice,
  MESA_IDirect3DDevice7Impl_ApplyStateBlock,
  MESA_IDirect3DDevice7Impl_CaptureStateBlock,
  MESA_IDirect3DDevice7Impl_DeleteStateBlock,
  MESA_IDirect3DDevice7Impl_CreateStateBlock,
  MESA_IDirect3DDevice7Impl_Load,
  MESA_IDirect3DDevice7Impl_LightEnable,
  0xdead0004, /* GetLightEnable */
  0xdead0005, /* SetClipPlane */
  0xdead0006, /* GetClipPlane */
  MESA_IDirect3DDevice7Impl_GetInfo
};

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
#undef XCAST
#endif

