/*	Direct3D OpenGL driver
 *
 * Copyright 2001-2002 TransGaming Technologies Inc.
 */
#include "config.h"

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

#include "wine/debug.h"
#include "ddrawi.h"
#include "d3d9.h"
#include "d3dhal.h"
#include "d3dgl_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

/* stuff we use that are still not in SGI's glext.h */
/* token values taken from http://oss.sgi.com/projects/ogl-sample/registry/ */
#ifndef GL_ARB_texture_border_clamp
#define GL_CLAMP_TO_BORDER_ARB		0x812D
#endif
#ifndef GL_ARB_texture_env_combine
#define GL_SUBTRACT_ARB			0x84E7
#endif
#ifndef GL_ARB_texture_env_dot3
#define GL_DOT3_RGB_ARB			0x86AE
#define GL_DOT3_RGBA_ARB		0x86AF
#endif
#ifndef GL_EXT_texture_env_dot3 /* ext 220 */
#define GL_DOT3_RGB_EXT			0x8740
#define GL_DOT3_RGBA_EXT		0x8741
#endif
#ifndef GL_ARB_texture_mirrored_repeat
#define GL_MIRRORED_REPEAT_ARB		0x8370
#endif
#ifndef GL_ATI_texture_mirror_once /* ext 221 */
#define GL_MIRROR_CLAMP_TO_EDGE_ATI	0x8743
#endif
#ifndef GL_ATI_texture_env_combine3 /* ext 279 */
#define GL_MODULATE_ADD_ATI		0x8744
#endif

/* legacy texture stage states */
#define D3DTSS_ADDRESS		12
#define D3DTSS_ADDRESSU		13
#define D3DTSS_ADDRESSV		14
#define D3DTSS_ADDRESSW		25
#define D3DTSS_BORDERCOLOR	15
#define D3DTSS_MAGFILTER	16
#define D3DTSS_MINFILTER	17
#define D3DTSS_MIPFILTER	18
#define D3DTSS_MIPMAPLODBIAS	19
#define D3DTSS_MAXMIPLEVEL	20
#define D3DTSS_MAXANISOTROPY	21

static inline float GL_RSF(DWORD dwState) ATTR_PURE;
static inline float GL_RSF(DWORD dwState)
{
    return *(float*)&dwState;
}

static inline void GL_RS2Col(GLfloat col[4], DWORD dwState) ATTR_PURE;
static inline void GL_RS2Col(GLfloat col[4], DWORD dwState)
{
    D3DCOLOR rcol = (D3DCOLOR)dwState;
    col[0] = RGBA_GETRED(rcol)   / 255.0;
    col[1] = RGBA_GETGREEN(rcol) / 255.0;
    col[2] = RGBA_GETBLUE(rcol)  / 255.0;
    col[3] = RGBA_GETALPHA(rcol) / 255.0;
}

static inline GLenum GL_tgt_par(LPD3D_T tex) ATTR_PURE;
static inline GLenum GL_tgt_par(LPD3D_T tex)
{
    if (SURF_MORE(tex)->ddsCapsEx.dwCaps2 & DDSCAPS2_CUBEMAP)
	return GL_TEXTURE_CUBE_MAP_ARB;
    return GL_TEXTURE_2D;
}

typedef const struct _GL_TEXOP {
    DD_GL_ext ext;
    DWORD dwFlags;
    GLint scale, cmode, amode, src0, src1, src2, src3;
} GL_TEXOP;

#define E_GL 0
#define E_EA EXT_texture_env_add
#define E_EC EXT_texture_env_combine
#define E_AC ARB_texture_env_combine
#define E_E3 EXT_texture_env_dot3
#define E_A3 ARB_texture_env_dot3
#define E_C3 ATI_texture_env_combine3
#define E_C4 NV_texture_env_combine4

/* If a SOURCE bit is set, that src value tells that a D3D texop argument should be
 * mapped and substituted to the corresponding GL source. If not, the src value is
 * considered a GL token, and is used directly (with a default operand), if non-zero.
 * For instance, for color ops, if dwFlags = GLTF_SOURCE0, src0 = 0, will mean that
 * D3DTSS_COLORARG1 will be mapped into GL_SOURCE0_RGB_EXT. Similarly, src0 = 1 will
 * map D3DTSS_COLORARG2 into GL_SOURCE0_RGB_EXT instead. */
#define GLTF_SOURCE0	0x0001
#define GLTF_SOURCE1	0x0002
#define GLTF_SOURCE2	0x0004
#define GLTF_SOURCE3	0x0008
#define GLTF_SOURCEA	GLTF_SOURCE0|GLTF_SOURCE1
#define GLTF_SOURCEB	GLTF_SOURCE1|GLTF_SOURCE2
#define GLTF_SOURCEC	GLTF_SOURCE0|GLTF_SOURCE1|GLTF_SOURCE2
#define GLTF_SOURCED	GLTF_SOURCE0|GLTF_SOURCE2
#define GLTF_COMPL0	0x0010
#define GLTF_COMPL1	0x0020
#define GLTF_COMPL2	0x0040
#define GLTF_COMPL3	0x0080
#define GLTF_COMPLD	GLTF_COMPL0|GLTF_COMPL2
#define GLTF_ALPHA0	0x0100
#define GLTF_ALPHA1	0x0200
#define GLTF_ALPHA2	0x0400
#define GLTF_ALPHA3	0x0800
#define GLTF_SOURCEI	GLTF_SOURCEA|GLTF_ALPHA2
#define GLTF_SOURCEBA	GLTF_SOURCED|GLTF_ALPHA1|GLTF_ALPHA3
#define GLTF_SOURCEBAP	GLTF_SOURCED|GLTF_COMPL1|GLTF_ALPHA3
#define GLTF_SOURCEL	GLTF_SOURCEC|GLTF_SOURCE3
/* Modifiers, not implemented or well-defined yet. */
#define GLTF_SA		0x1000	/* Special alpha treatment */
#define GLTF_C		0x2000	/* Use a texture environment constant */
#define GLTF_NC		0x4000	/* Don't use combiners */

/* If you implement anything, also try to update caps bits in ddraw/gl.c */

/* this table is indexed by the table below */
static GL_TEXOP GL_tex_op_data[] = {
/* used when alpha is disabled */
    /* DISABLE */			{E_GL, 0,            1, GL_REPLACE, 0, GL_PREVIOUS_EXT},
/* supported by OpenGL 1.2 */
    /* SELECTARG1 */			{E_GL, GLTF_SOURCE0, 1, GL_REPLACE, 0, 0},
    /* SELECTARG2 */			{E_GL, GLTF_SOURCE0, 1, GL_REPLACE, 0, 1},
    /* MODULATE */			{E_GL, GLTF_SOURCEA, 1, GL_MODULATE, 0, 0, 1},
/* supported by ARB_texture_env_add */
    /* ADD */				{E_EA, GLTF_SOURCEA, 1, GL_ADD, 0, 0, 1},
/* supported by ARB_texture_env_combine */
    /* ADDSMOOTH */			{E_AC, GLTF_SOURCEB|GLTF_C, 1, GL_INTERPOLATE_EXT, 0, GL_CONSTANT_EXT, 1, 0},
    /* ADDSMOOTH */			{E_GL, GLTF_SOURCEA|GLTF_C|GLTF_NC, 1, GL_BLEND, 0, 0, 1},
    /* SUBTRACT */			{E_AC, GLTF_SOURCEA, 1, GL_SUBTRACT_ARB, 0, 0, 1},
/* supported by EXT_texture_env_combine */
    /* MODULATE2X */			{E_EC, GLTF_SOURCEA, 2, GL_MODULATE, 0, 0, 1},
    /* MODULATE4X */			{E_EC, GLTF_SOURCEA, 4, GL_MODULATE, 0, 0, 1},
    /* ADDSIGNED */			{E_EC, GLTF_SOURCEA, 1, GL_ADD_SIGNED_EXT, 0, 0, 1},
    /* ADDSIGNED2X */			{E_EC, GLTF_SOURCEA, 2, GL_ADD_SIGNED_EXT, 0, 0, 1},
    /* BLENDDIFFUSEALPHA */		{E_EC, GLTF_SOURCEI, 1, GL_INTERPOLATE_EXT, 0, 0, 1, GL_PRIMARY_COLOR_EXT},
    /* BLENDTEXTUREALPHA */		{E_EC, GLTF_SOURCEI, 1, GL_INTERPOLATE_EXT, 0, 0, 1, GL_TEXTURE},
    /* BLENDTEXTUREALPHA */		{E_GL, GLTF_SOURCEA, 1, GL_DECAL, 0, 0, 1},
    /* BLENDFACTORALPHA */		{E_EC, GLTF_SOURCEI, 1, GL_INTERPOLATE_EXT, 0, 0, 1, GL_CONSTANT_EXT},
    /* BLENDCURRENTALPHA */		{E_EC, GLTF_SOURCEI, 1, GL_INTERPOLATE_EXT, 0, 0, 1, GL_PREVIOUS_EXT},
    /* LERP */				{E_EC, GLTF_SOURCEC, 1, GL_INTERPOLATE_EXT, 0, 1, 2, 0},
/* supported by ARB_texture_env_dot3 or EXT_texture_env_dot3 (incompatible extensions) */
    /* DOTPRODUCT3 */			{E_A3, GLTF_SOURCEA|GLTF_SA, 1, GL_DOT3_RGB_ARB, GL_DOT3_RGBA_ARB, 0, 1},
    /* DOTPRODUCT3 */			{E_E3, GLTF_SOURCEA|GLTF_SA, 1, GL_DOT3_RGB_EXT, GL_DOT3_RGBA_EXT, 0, 1},
/* supported by ATI_texture_env_combine3 */
    /* BLENDTEXTUREALPHAPM */		{E_C3, GLTF_SOURCEA|GLTF_ALPHA2, 1, GL_MODULATE_ADD_ATI, 0, 1, 0, GL_TEXTURE},
    /* MODULATEALPHA_ADDCOLOR */	{E_C3, GLTF_SOURCEC|GLTF_ALPHA0, 1, GL_MODULATE_ADD_ATI, 0, 0, 0, 1},
    /* MODULATECOLOR_ADDALPHA */	{E_C3, GLTF_SOURCEC|GLTF_ALPHA1, 1, GL_MODULATE_ADD_ATI, 0, 0, 0, 1},
    /* MODULATEINVALPHA_ADDCOLOR */	{E_C3, GLTF_SOURCEC|GLTF_COMPL0|GLTF_ALPHA0, 1, GL_MODULATE_ADD_ATI, 0, 0, 0, 1},
    /* MODULATEINVCOLOR_ADDALPHA */	{E_C3, GLTF_SOURCEC|GLTF_COMPL0|GLTF_ALPHA1, 1, GL_MODULATE_ADD_ATI, 0, 0, 0, 1},
    /* MULTIPLYADD */			{E_C3, GLTF_SOURCEC, 1, GL_MODULATE_ADD_ATI, 0, 1, 0, 2}
};

static struct {
    int c;
    GL_TEXOP *op;
    const char *name;
} GL_tex_op[] = {
    {1,&GL_tex_op_data[ 0],NULL},
    {1,&GL_tex_op_data[ 0],"DISABLE"},
    {1,&GL_tex_op_data[ 1],"SELECTARG1"},
    {1,&GL_tex_op_data[ 2],"SELECTARG2"},
    {1,&GL_tex_op_data[ 3],"MODULATE"},
    {1,&GL_tex_op_data[ 8],"MODULATE2X"},
    {1,&GL_tex_op_data[ 9],"MODULATE4X"},
    {1,&GL_tex_op_data[ 4],"ADD"},
    {1,&GL_tex_op_data[10],"ADDSIGNED"},
    {1,&GL_tex_op_data[11],"ADDSIGNED2X"},
    {1,&GL_tex_op_data[ 7],"SUBTRACT"},
    {2,&GL_tex_op_data[ 5],"ADDSMOOTH"},
    {1,&GL_tex_op_data[12],"BLENDDIFFUSEALPHA"},
    {2,&GL_tex_op_data[13],"BLENDTEXTUREALPH"},
    {1,&GL_tex_op_data[15],"BLENDFACTORALPHA"},
    {1,&GL_tex_op_data[20],"BLENDTEXTUREALPHAPM"},
    {1,&GL_tex_op_data[16],"BLENDCURRENTALPHA"},
    {0,NULL,               "PREMODULATE"},
    {1,&GL_tex_op_data[21],"MODULATEALPHA_ADDCOLOR"},
    {1,&GL_tex_op_data[22],"MODULATECOLOR_ADDALPHA"},
    {1,&GL_tex_op_data[23],"MODULATEINVALPHA_ADDCOLOR"},
    {1,&GL_tex_op_data[24],"MODULATEINVCOLOR_ADDALPHA"},
    {0,NULL,               "BUMPENVMAP"},
    {0,NULL,               "BUMPENVMAPLUMINANCE"},
    {2,&GL_tex_op_data[18],"DOTPRODUCT3"},
    {1,&GL_tex_op_data[25],"MULTIPLYADD"},
    {1,&GL_tex_op_data[17],"LERP"}
};

/* this table expresses anything possible to express through NV_texture_env_combine4,
 * this is needed because NV_texture_env_combine4 is "exclusive": a texture unit
 * can't mix NV_texture_env_combine4 for the color with EXT_texture_env_combine for the alpha,
 * so if the color op depends on combine4, we must also express the alpha op with it */
static GL_TEXOP GL_combine4_op[] = {
					{E_C4, GLTF_COMPL1,                1, GL_ADD, 0, GL_PREVIOUS_EXT, GL_ZERO, GL_ZERO, GL_ZERO},
    /* DISABLE */			{E_C4, GLTF_COMPL1,                1, GL_ADD, 0, GL_PREVIOUS_EXT, GL_ZERO, GL_ZERO, GL_ZERO},
    /* SELECTARG1 */			{E_C4, GLTF_SOURCE0|GLTF_COMPL1,   1, GL_ADD, 0, 0, GL_ZERO, GL_ZERO, GL_ZERO},
    /* SELECTARG2 */			{E_C4, GLTF_SOURCE0|GLTF_COMPL1,   1, GL_ADD, 0, 1, GL_ZERO, GL_ZERO, GL_ZERO},
    /* MODULATE */			{E_C4, GLTF_SOURCEA,               1, GL_ADD, 0, 0, 1,       GL_ZERO, GL_ZERO},
    /* MODULATE2X */			{E_C4, GLTF_SOURCEA,               2, GL_ADD, 0, 0, 1,       GL_ZERO, GL_ZERO},
    /* MODULATE4X */			{E_C4, GLTF_SOURCEA,               4, GL_ADD, 0, 0, 1,       GL_ZERO, GL_ZERO},
    /* ADD */				{E_C4, GLTF_SOURCED|GLTF_COMPLD,   1, GL_ADD, 0, 0, GL_ZERO, 1,       GL_ZERO},
    /* ADDSIGNED */			{E_C4, GLTF_SOURCED|GLTF_COMPLD,   1, GL_ADD_SIGNED_EXT, 0, 0, GL_ZERO, 1, GL_ZERO},
    /* ADDSIGNED2X */			{E_C4, GLTF_SOURCED|GLTF_COMPLD,   2, GL_ADD_SIGNED_EXT, 0, 0, GL_ZERO, 1, GL_ZERO},
    /* SUBTRACT - is it possible? */	{-1},
    /* ADDSMOOTH */			{E_C4, GLTF_SOURCEC|GLTF_COMPL1,   1, GL_ADD, 0, 0, 1,       1,       GL_ZERO},
    /* BLENDDIFFUSEALPHA */		{E_C4, GLTF_SOURCEBA|GLTF_COMPL3,  1, GL_ADD, 0, 0, GL_PRIMARY_COLOR_EXT, 1, GL_PRIMARY_COLOR_EXT},
    /* BLENDTEXTUREALPHA */		{E_C4, GLTF_SOURCEBA|GLTF_COMPL3,  1, GL_ADD, 0, 0, GL_TEXTURE,           1, GL_TEXTURE},
    /* BLENDFACTORALPHA */		{E_C4, GLTF_SOURCEBA|GLTF_COMPL3,  1, GL_ADD, 0, 0, GL_CONSTANT_EXT,      1, GL_CONSTANT_EXT},
    /* BLENDTEXTUREALPHAPM */		{E_C4, GLTF_SOURCEBAP|GLTF_COMPL3, 1, GL_ADD, 0, 0, GL_ZERO,              1, GL_TEXTURE},
    /* BLENDCURRENTALPHA */		{E_C4, GLTF_SOURCEBA|GLTF_COMPL3,  1, GL_ADD, 0, 0, GL_PREVIOUS_EXT,      1, GL_PREVIOUS_EXT},
    /* PREMODULATE - is it possible? */	{-1},
    /* MODULATEALPHA_ADDCOLOR */	{E_C4, GLTF_SOURCEA|GLTF_ALPHA0|GLTF_COMPL3, 1, GL_ADD, 0, 0, 1, 0, GL_ZERO},
    /* MODULATECOLOR_ADDALPHA */	{E_C4, GLTF_SOURCEA|GLTF_ALPHA2|GLTF_COMPL3, 1, GL_ADD, 0, 0, 1, 0, GL_ZERO},
    /* MODULATEINVALPHA_ADDCOLOR */	{E_C4, GLTF_SOURCEA|GLTF_COMPL0|GLTF_ALPHA0|GLTF_COMPL3, 1, GL_ADD, 0, 0, 1, 0, GL_ZERO},
    /* MODULATEINVCOLOR_ADDALPHA */	{E_C4, GLTF_SOURCEA|GLTF_COMPL0|GLTF_ALPHA2|GLTF_COMPL3, 1, GL_ADD, 0, 0, 1, 0, GL_ZERO},
    /* BUMPENVMAP */			{-1},
    /* BUMPENVMAPLUMINANCE */		{-1},
    /* DOTPRODUCT3 */			{-1},
    /* MULTIPLYADD */			{E_C4, GLTF_SOURCEC|GLTF_COMPL3,   1, GL_ADD, 0, 0, 1, 2, GL_ZERO},
    /* LERP */				{E_C4, GLTF_SOURCEL|GLTF_COMPL3,   1, GL_ADD, 0, 0, 2, 1, 2},
};

static const GLint GL_D3DTA[] = {
    GL_PRIMARY_COLOR_EXT, /* diffuse */
    GL_PREVIOUS_EXT,
    GL_TEXTURE,
    GL_CONSTANT_EXT,
    GL_PRIMARY_COLOR_EXT /* specular, not supported by GL yet */
};

static const GLenum GL_mag[] = {
    GL_NEAREST, /* NONE */
    GL_NEAREST,	/* POINT */
    GL_LINEAR,	/* LINEAR */
    GL_LINEAR,	/* FLATCUBIC */
    GL_LINEAR,	/* GAUSSIANCUBIC */
    GL_LINEAR	/* ANISOTROPIC */
};

static const GLenum GL_mip_min[4][4] = {
/* min: POINT                               LINEAR                    ANISOTROPIC */
    {0, 0,                                  0,                        0},			/* mip: */
    {GL_NEAREST, GL_NEAREST,                GL_LINEAR,                GL_LINEAR},		/* NONE */
    {GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST},/* POINT */
    {GL_NEAREST, GL_NEAREST_MIPMAP_LINEAR,  GL_LINEAR_MIPMAP_LINEAR,  GL_LINEAR_MIPMAP_LINEAR}	/* LINEAR */
};

static const GLenum GL_tex_addr[] = {
    GL_REPEAT,                          /* invalid, but use WRAP */
    GL_REPEAT,				/* WRAP */
    GL_MIRRORED_REPEAT_ARB,		/* MIRROR */
    GL_CLAMP_TO_EDGE,			/* CLAMP */
    GL_CLAMP_TO_BORDER_ARB,		/* BORDER */
    GL_MIRROR_CLAMP_TO_EDGE_ATI		/* MIRRORONCE */
};

/* Returns whether we can use a particular extension */
static inline BOOL D3D_GL_have_tex_ext(const GL_D3D_priv* priv, DD_GL_ext ext) ATTR_PURE;
static inline BOOL D3D_GL_have_tex_ext(const GL_D3D_priv* priv, DD_GL_ext ext)
{
    if (ext == E_GL) return TRUE;

    if (HAVE_GL_EXT(priv, ext)) return TRUE;

    /* In case there are GL implementations that report ARB but not EXT */
    if (ext == EXT_texture_env_add) return HAVE_GL_EXT(priv, ARB_texture_env_add);
    if (ext == EXT_texture_env_combine) return HAVE_GL_EXT(priv, ARB_texture_env_combine);

    return FALSE;
}

/* Find the most appropriate texture operation descriptor from GL_tex_op table
 * Returns NULL if no available GL extension can support the requested operation
 */
static inline GL_TEXOP* D3D_GL_get_tex_op(const GL_D3D_priv* priv, DWORD op, GL_TEXOP* comp) ATTR_PURE;
static inline GL_TEXOP* D3D_GL_get_tex_op(const GL_D3D_priv* priv, DWORD op, GL_TEXOP* comp)
{
    int c;
    GL_TEXOP* gop;
    if (comp && comp->ext == E_C4) {
	/* if color op already needs C4, the alpha op *must* also use it */
	gop = &GL_combine4_op[op];
	return (gop->ext == E_C4) ? gop : NULL;
    }
    c = GL_tex_op[op].c;
    gop = GL_tex_op[op].op;
    while (c) {
	if (D3D_GL_have_tex_ext(priv, gop->ext)) return gop;
	gop++;
	c--;
    }
    if (!comp && HAVE_GL_EXT(priv, NV_texture_env_combine4)) {
	/* if it's a color op, we can try C4 */
	gop = &GL_combine4_op[op];
	return (gop->ext == E_C4) ? gop : NULL;
    }
    return NULL;
}

#define OPERAND_OFS (GL_OPERAND0_RGB_EXT - GL_SOURCE0_RGB_EXT)

/* Compute GL_OPERANDn_{RGB|ALPHA}_EXT parameter from D3D tex-op argument */
static inline GLenum GL_get_alpha_opn(DWORD dwValue) ATTR_PURE;
static inline GLenum GL_get_alpha_opn(DWORD dwValue)
{
    if (dwValue & D3DTA_COMPLEMENT) return GL_ONE_MINUS_SRC_ALPHA;
    return GL_SRC_ALPHA;
}

static inline GLenum GL_get_color_opn(DWORD dwValue) ATTR_PURE;
static inline GLenum GL_get_color_opn(DWORD dwValue)
{
    if (dwValue & D3DTA_ALPHAREPLICATE) return GL_get_alpha_opn(dwValue);
    if (dwValue & D3DTA_COMPLEMENT) return GL_ONE_MINUS_SRC_COLOR;
    return GL_SRC_COLOR;
}

static inline DWORD GL_get_compl(GL_TEXOP* gop, DWORD flag) ATTR_PURE;
static inline DWORD GL_get_compl(GL_TEXOP* gop, DWORD flag)
{
    if (gop->dwFlags & flag) return D3DTA_COMPLEMENT;
    return 0;
}

static inline DWORD GL_get_alpha(GL_TEXOP* gop, DWORD flag) ATTR_PURE;
static inline DWORD GL_get_alpha(GL_TEXOP* gop, DWORD flag)
{
    if (gop->dwFlags & flag) return D3DTA_ALPHAREPLICATE;
    return 0;
}

/* Template for inline functions to be given to COND_TEX_ENV.
 * One such function is instantiated for every possible GL tex-op source.
 * Template takes:
 *  name = name of function
 *  flag,src = GL tex-op source (see GLTF flags above)
 *  arg1,arg2 = D3D argument fields
 *  opn_func = function to compute GL_OPERANDn_{RGB|ALPHA}_EXT parameter from D3D argument
 *  opn_def = default GL_OPERANDn_{RGB|ALPHA}_EXT if D3D arguments are not used
 * Instantiated function takes:
 *  dwStage = texture stage to take any tex-op arguments from
 *  gop = texture operation descriptor
 * Function returns:
 *  src = parameter for GL_SOURCEn_{RGB|ALPHA}_EXT
 *  opn = parameter for GL_OPERANDn_{RGB|ALPHA}_EXT
 */
#define MAKE_TEX_SRC(name,flag,src,arg1,arg2,arg0,opn_func) \
static inline void name(const GL_D3D_priv* priv, DWORD dwStage, GL_TEXOP* gop, GLenum* src, GLenum* opn) \
{ \
    if (gop->dwFlags & (flag)) { \
	DWORD dwValue = (((gop->src == 2) ? priv->state.texstagestate[dwStage][D3DTSS_##arg0] : \
			  (gop->src == 1) ? priv->state.texstagestate[dwStage][D3DTSS_##arg2] : \
			  (gop->src == 0) ? priv->state.texstagestate[dwStage][D3DTSS_##arg1] : 0) \
			 ^ GL_get_compl(gop, (flag) << 4)) \
			| GL_get_alpha(gop, (flag) << 8); \
	*src = GL_D3DTA[dwValue & D3DTA_SELECTMASK]; \
	*opn = opn_func(dwValue); \
    } else { \
	*src = gop->src; \
	*opn = opn_func(GL_get_compl(gop, (flag) << 4) | \
			GL_get_alpha(gop, (flag) << 8)); \
    } \
}

MAKE_TEX_SRC(GL_get_color_src0,GLTF_SOURCE0,src0,COLORARG1,COLORARG2,COLORARG0,GL_get_color_opn)
MAKE_TEX_SRC(GL_get_color_src1,GLTF_SOURCE1,src1,COLORARG1,COLORARG2,COLORARG0,GL_get_color_opn)
MAKE_TEX_SRC(GL_get_color_src2,GLTF_SOURCE2,src2,COLORARG1,COLORARG2,COLORARG0,GL_get_color_opn)
MAKE_TEX_SRC(GL_get_color_src3,GLTF_SOURCE3,src3,COLORARG1,COLORARG2,COLORARG0,GL_get_color_opn)
MAKE_TEX_SRC(GL_get_alpha_src0,GLTF_SOURCE0,src0,ALPHAARG1,ALPHAARG2,ALPHAARG0,GL_get_alpha_opn)
MAKE_TEX_SRC(GL_get_alpha_src1,GLTF_SOURCE1,src1,ALPHAARG1,ALPHAARG2,ALPHAARG0,GL_get_alpha_opn)
MAKE_TEX_SRC(GL_get_alpha_src2,GLTF_SOURCE2,src2,ALPHAARG1,ALPHAARG2,ALPHAARG0,GL_get_alpha_opn)
MAKE_TEX_SRC(GL_get_alpha_src3,GLTF_SOURCE3,src3,ALPHAARG1,ALPHAARG2,ALPHAARG0,GL_get_alpha_opn)

#undef MAKE_TEX_SRC

static BOOL GL_IsStageUsed(const GL_D3D_priv* priv, DWORD dwStage)
{
    if (!priv->state.texstagestate[dwStage][D3DTSS_TEXTUREMAP]) {
	DWORD arg1, arg2;
	/* if the app references a texture without having a texture selected,
	 * disable the unit */
	arg1 = priv->state.texstagestate[dwStage][D3DTSS_COLORARG1];
	arg2 = priv->state.texstagestate[dwStage][D3DTSS_COLORARG2];
	if (priv->state.texstagestate[dwStage][D3DTSS_COLOROP] == D3DTOP_SELECTARG1)
	    return ((arg1 & D3DTA_SELECTMASK) != D3DTA_TEXTURE);
	if (priv->state.texstagestate[dwStage][D3DTSS_COLOROP] == D3DTOP_SELECTARG2)
	    return ((arg2 & D3DTA_SELECTMASK) != D3DTA_TEXTURE);
	if ((priv->state.texstagestate[dwStage][D3DTSS_COLOROP] >= D3DTOP_MULTIPLYADD) &&
	    (priv->state.texstagestate[dwStage][D3DTSS_COLORARG0] & D3DTA_SELECTMASK) == D3DTA_TEXTURE)
	    return FALSE;
	return (((arg1 & D3DTA_SELECTMASK) != D3DTA_TEXTURE) &&
		((arg2 & D3DTA_SELECTMASK) != D3DTA_TEXTURE));
    }
    if (!D3D_GL_have_tex_ext(priv, EXT_texture_env_combine)) {
	DWORD arg = D3DTA_TEXTURE;
	/* if the app requests D3DTA_CURRENT (or D3DTA_DIFFUSE in stage 0) in
	 * a SELECTARG operation, then disabling the texture unit is the only
	 * way to get the desired pass-through operation with standard OpenGL */
	if (priv->state.texstagestate[dwStage][D3DTSS_COLOROP] == D3DTOP_SELECTARG1)
	    arg = priv->state.texstagestate[dwStage][D3DTSS_COLORARG1];
	if (priv->state.texstagestate[dwStage][D3DTSS_COLOROP] == D3DTOP_SELECTARG2)
	    arg = priv->state.texstagestate[dwStage][D3DTSS_COLORARG2];
	if ((arg & D3DTA_SELECTMASK) != D3DTA_TEXTURE) return FALSE;
    }
    return TRUE;
}

static inline BOOL GL_IsStageEnabled(const GL_D3D_priv* priv, DWORD dwStage)
{
    unsigned u;
    for (u=0; u<=dwStage; u++)
	if (priv->state.texstagestate[u][D3DTSS_COLOROP] <= D3DTOP_DISABLE)
	    return FALSE;
    return GL_IsStageUsed(priv, dwStage);
}

static inline void GL_UpdateStageEnable(GL_D3D_priv* priv, DWORD dwStage, BOOL use)
{
    if (use) {
	if (!priv->texture_used[dwStage] && priv->texture_target[dwStage]) {
	    TRACE(" enabling unit %ld\n", dwStage);
	    glEnable(priv->texture_target[dwStage]);
	    priv->texture_used[dwStage] = TRUE;
	}
    } else {
	if (priv->texture_used[dwStage]) {
	    TRACE(" disabling unit %ld\n", dwStage);
	    glDisable(priv->texture_target[dwStage]);
	    priv->texture_used[dwStage] = FALSE;
	}
    }
}

static inline void GL_StageUpdated(GL_D3D_priv* priv, DWORD dwStage)
{
    GL_UpdateStageEnable(priv, dwStage, GL_IsStageEnabled(priv, dwStage));
}

static inline void GL_StageEnabled(GL_D3D_priv* priv, DWORD dwStage)
{
    unsigned u;
    for (u=0; u<=dwStage; u++)
	if (priv->state.texstagestate[u][D3DTSS_COLOROP] <= D3DTOP_DISABLE)
	    return;
    GL_UpdateStageEnable(priv, dwStage, GL_IsStageUsed(priv, dwStage));
    for (u=dwStage+1; u<priv->lpCaps->wMaxTextureBlendStages &&
		      priv->state.texstagestate[u][D3DTSS_COLOROP]>D3DTOP_DISABLE; u++) {
	GLD(priv)->glActiveTextureARB(GL_TEXTURE0_ARB + u);
	GL_UpdateStageEnable(priv, u, GL_IsStageUsed(priv, u));
    }
}

static inline void GL_StageDisabled(GL_D3D_priv* priv, DWORD dwStage)
{
    unsigned u;
    GL_UpdateStageEnable(priv, dwStage, FALSE);
    for (u=dwStage+1; u<priv->lpCaps->wMaxTextureBlendStages &&
		      priv->state.texstagestate[u][D3DTSS_COLOROP]>D3DTOP_DISABLE; u++) {
	GLD(priv)->glActiveTextureARB(GL_TEXTURE0_ARB + u);
	GL_UpdateStageEnable(priv, u, FALSE);
    }
}

static void D3D_GL_update_texture_factor(GL_D3D_priv* priv, GL_TEXOP* cop, GL_TEXOP* aop)
{
    static const GLfloat one4f[4] = {1.0, 1.0, 1.0, 1.0};
    const GLfloat *factor = priv->texture_factor;
    if ((cop && (cop->dwFlags & GLTF_C)) || (aop && (aop->dwFlags & GLTF_C)))
	factor = one4f;
    TRACE(" factor (%f,%f,%f,%f)\n", factor[0], factor[1], factor[2], factor[3]);
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, factor);
}

static HRESULT D3D_GL_validate_tex_stage(GL_D3D_priv* priv, DWORD dwStage)
{
    GL_TEXOP *cop = D3D_GL_get_tex_op(priv, priv->state.texstagestate[dwStage][D3DTSS_COLOROP], NULL);
    GL_TEXOP *aop = D3D_GL_get_tex_op(priv, priv->state.texstagestate[dwStage][D3DTSS_ALPHAOP], cop);
    GLenum csrc0, csrc1, csrc2;
    GLenum copn0, copn1, copn2;
    GLenum asrc0, asrc1, asrc2;
    GLenum aopn0, aopn1, aopn2;

    if (!cop) return D3DERR_UNSUPPORTEDCOLOROPERATION;
    if (!aop) return D3DERR_UNSUPPORTEDALPHAOPERATION;

    GL_get_color_src0(priv, dwStage, cop, &csrc0, &copn0);
    GL_get_color_src1(priv, dwStage, cop, &csrc1, &copn1);
    GL_get_color_src2(priv, dwStage, cop, &csrc2, &copn2);
    GL_get_alpha_src0(priv, dwStage, aop, &asrc0, &aopn0);
    GL_get_alpha_src1(priv, dwStage, aop, &asrc1, &aopn1);
    GL_get_alpha_src2(priv, dwStage, aop, &asrc2, &aopn2);

    if (D3D_GL_have_tex_ext(priv, EXT_texture_env_combine)) {
	if (cop->dwFlags & GLTF_NC) goto no_combiner;
	if ((cop->dwFlags & GLTF_C) || (aop->dwFlags & GLTF_C)) {
	    /* if we use the texture constant color ourselves, the app can't use it */
	    if (((csrc0 == GL_CONSTANT_EXT) && (cop->dwFlags & GLTF_SOURCE0)) ||
		((csrc1 == GL_CONSTANT_EXT) && (cop->dwFlags & GLTF_SOURCE1)) ||
		((csrc2 == GL_CONSTANT_EXT) && (cop->dwFlags & GLTF_SOURCE2)))
		return D3DERR_UNSUPPORTEDCOLORARG;
	    if (((asrc0 == GL_CONSTANT_EXT) && (aop->dwFlags & GLTF_SOURCE0)) ||
		((asrc1 == GL_CONSTANT_EXT) && (aop->dwFlags & GLTF_SOURCE1)) ||
		((asrc2 == GL_CONSTANT_EXT) && (aop->dwFlags & GLTF_SOURCE2)))
		return D3DERR_UNSUPPORTEDALPHAARG;
	}
	if ((cop->dwFlags & GLTF_SA) && (cop == aop)) {
	    /* if the alpha op is an extension of the color op, alpha args must match color args */
	    /* FIXME: better copn/aopn checking */
	    if (csrc0 != asrc0 /* || copn0 != aopn0 */ ||
		csrc1 != asrc1 /* || copn1 != aopn1 */ ||
		csrc2 != asrc2 /* || copn2 != aopn2 */ ) return D3DERR_UNSUPPORTEDALPHAARG;
	}
	return D3D_OK;
    }

no_combiner:
    /* we don't have texture combiners, validate what can be expressed without them */

    if (dwStage == 0) {
	if (csrc0 == GL_PRIMARY_COLOR_EXT) csrc0 = GL_PREVIOUS_EXT;
	if (csrc1 == GL_PRIMARY_COLOR_EXT) csrc1 = GL_PREVIOUS_EXT;
	if (csrc2 == GL_PRIMARY_COLOR_EXT) csrc2 = GL_PREVIOUS_EXT;
	if (asrc0 == GL_PRIMARY_COLOR_EXT) asrc0 = GL_PREVIOUS_EXT;
	if (asrc1 == GL_PRIMARY_COLOR_EXT) asrc1 = GL_PREVIOUS_EXT;
	if (asrc2 == GL_PRIMARY_COLOR_EXT) asrc2 = GL_PREVIOUS_EXT;
    }

    /* REPLACE */
    if (cop->cmode == GL_REPLACE) {
	if (csrc0 != GL_TEXTURE && csrc0 != GL_PREVIOUS_EXT) return D3DERR_UNSUPPORTEDCOLORARG;
	if (copn0 != GL_SRC_COLOR) return D3DERR_UNSUPPORTEDCOLORARG;
	if (aop->cmode != GL_REPLACE) return D3DERR_UNSUPPORTEDALPHAOPERATION;
	if (asrc0 != csrc0) return D3DERR_UNSUPPORTEDALPHAARG;
	if (aopn0 != GL_SRC_ALPHA) return D3DERR_UNSUPPORTEDALPHAARG;
	return D3D_OK;
    }

    /* MODULATE, BLEND, DECAL and ADD */
    if (cop->cmode == GL_BLEND || cop->cmode == GL_DECAL) {
	if (!(csrc0 == GL_TEXTURE && csrc1 == GL_PREVIOUS_EXT))
	    return D3DERR_UNSUPPORTEDCOLORARG;
    }
    else {
	if (!((csrc0 == GL_TEXTURE && csrc1 == GL_PREVIOUS_EXT) ||
	      (csrc1 == GL_TEXTURE && csrc0 == GL_PREVIOUS_EXT)))
	    return D3DERR_UNSUPPORTEDCOLORARG;
    }
    if (copn0 != GL_SRC_COLOR || copn1 != GL_SRC_COLOR)
	return D3DERR_UNSUPPORTEDCOLORARG;

    if (aop->cmode == GL_REPLACE) {
	/* FIXME: validate this only for textures without alpha channel
	 * (except when GL_DECAL is used) */
	if (asrc0 != GL_PREVIOUS_EXT) return D3DERR_UNSUPPORTEDALPHAARG;
	if (aopn0 != GL_SRC_ALPHA) return D3DERR_UNSUPPORTEDALPHAARG;
	return D3D_OK;
    }
    else if (aop->cmode == GL_MODULATE) {
	/* FIXME: validate this only for textures with alpha channel */
	if (!((asrc0 == GL_TEXTURE && asrc1 == GL_PREVIOUS_EXT) ||
	      (asrc1 == GL_TEXTURE && asrc0 == GL_PREVIOUS_EXT)))
	    return D3DERR_UNSUPPORTEDALPHAARG;
	if (aopn0 != GL_SRC_ALPHA || aopn1 != GL_SRC_ALPHA)
	    return D3DERR_UNSUPPORTEDALPHAARG;
	return D3D_OK;
    }
    else return D3DERR_UNSUPPORTEDALPHAOPERATION;
}

void GL_LoadTextureFactor(GL_D3D_priv* priv, GLfloat col[4])
{
    unsigned u;

    memcpy(priv->texture_factor, col, sizeof(GLfloat)*4);
    for (u=0; u<priv->lpCaps->wMaxTextureBlendStages; u++) {
	GL_TEXOP *cop = D3D_GL_get_tex_op(priv, priv->state.texstagestate[u][D3DTSS_COLOROP], NULL);
	GL_TEXOP *aop = D3D_GL_get_tex_op(priv, priv->state.texstagestate[u][D3DTSS_ALPHAOP], cop);
	if (!cop) continue;
	if ((cop->dwFlags & GLTF_C) || (aop && (aop->dwFlags & GLTF_C)))
	    continue;
	GLD(priv)->glActiveTextureARB(GL_TEXTURE0_ARB + u);
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, col);
    }
}

#define STAGE_UPDATED 1

#define _GTSS(s,x) priv->state.texstagestate[s][x]
#define _CLR(s,x) do { priv->state.dirty_tss[s][(x) / 32] &= ~(1 << ((x) & 31)); } while (0)
#define _TSS(s,x) _GTSS(s,D3DTSS_##x)
#define _CTSS(s,x) _CLR(s,D3DTSS_##x)

#define _DWTSS(x) DWORD state = _TSS(Stage,x); _CTSS(Stage,x)
#define _DVTSS(x) float state = GL_RSF(_TSS(Stage,x)); _CTSS(Stage,x)
#define _COLTSS(x) GLfloat state[4]; GL_RS2Col(state, _TSS(Stage,x)); _CTSS(Stage,x)

typedef DWORD (*GL_texture_stage_state_handler)(GL_D3D_priv* priv, DWORD Stage, DWORD State);

/* Conditional Texture Environment.
 * Sets GL tex-op parameters as required by the (newly set) texture operation.
 * Assumes:
 *  Stage = texture stage
 * Takes:
 *  env = GL tex-op source to set
 *  gop = texture operation descriptor
 *  func = function to compute GL tex-op parameters
 */
#define COND_TEX_ENV(env, gop, func) \
    do { GLenum src, opn; \
	 func(priv, Stage, gop, &src, &opn); \
	 TRACE(" arg %04x %04x %04x\n", env, src, opn); \
	 if (src || gop->ext == E_C4) { \
	  glTexEnvi(GL_TEXTURE_ENV, env, src); \
	  glTexEnvi(GL_TEXTURE_ENV, env + OPERAND_OFS, opn); } } while (0)

/* although these routines are called from a table, they're declared inline anyway
 * since they're also being called from other routines (also called from the table). */

static inline DWORD D3D_GL_result_arg(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _CTSS(Stage,RESULTARG);
    /* we'd need NV_register_combiners or something to support this,
     * but since we don't advertise support for this, we'll ignore it */
    return 0;
}

static inline DWORD D3D_GL_alpha_arg(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _CTSS(Stage,ALPHAARG1);
    _CTSS(Stage,ALPHAARG2);
    _CTSS(Stage,ALPHAARG0);
    if (D3D_GL_have_tex_ext(priv, EXT_texture_env_combine)) {
	GL_TEXOP *aop = priv->alpha_op[Stage];
	COND_TEX_ENV(GL_SOURCE0_ALPHA_EXT, aop, GL_get_alpha_src0);
	COND_TEX_ENV(GL_SOURCE1_ALPHA_EXT, aop, GL_get_alpha_src1);
	COND_TEX_ENV(GL_SOURCE2_ALPHA_EXT, aop, GL_get_alpha_src2);
	COND_TEX_ENV(GL_SOURCE3_ALPHA_NV,  aop, GL_get_alpha_src3);
	return STAGE_UPDATED;
    }
    return 0;
}

static inline DWORD D3D_GL_color_arg(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _CTSS(Stage,COLORARG1);
    _CTSS(Stage,COLORARG2);
    _CTSS(Stage,COLORARG0);
    if (D3D_GL_have_tex_ext(priv, EXT_texture_env_combine)) {
	GL_TEXOP *cop = priv->color_op[Stage];
	COND_TEX_ENV(GL_SOURCE0_RGB_EXT, cop, GL_get_color_src0);
	COND_TEX_ENV(GL_SOURCE1_RGB_EXT, cop, GL_get_color_src1);
	COND_TEX_ENV(GL_SOURCE2_RGB_EXT, cop, GL_get_color_src2);
	COND_TEX_ENV(GL_SOURCE3_RGB_NV,  cop, GL_get_color_src3);
	return STAGE_UPDATED;
    }
    return 0;
}

static inline DWORD D3D_GL_alpha_op(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _CTSS(Stage,ALPHAOP);
    if (D3D_GL_have_tex_ext(priv, EXT_texture_env_combine)) {
	DWORD cstate = _TSS(Stage,COLOROP);
	DWORD astate = _TSS(Stage,ALPHAOP);
	GL_TEXOP *cop = priv->color_op[Stage];
	GL_TEXOP *aop = D3D_GL_get_tex_op(priv, astate, cop);
	GLint amode;
	priv->alpha_op[Stage] = aop;
	if ((astate <= D3DTOP_DISABLE) && (cstate <= D3DTOP_DISABLE)) {
	    TRACE(" stage disable\n");
	    return 0;
	}
	if (!aop) {
	    TRACE(" unsupported\n");
	    D3D_GL_update_texture_factor(priv, cop, aop);
	    return 0;
	}
	amode = aop->cmode;
	if (cop && (cop->dwFlags & GLTF_SA)) {
	    if (cstate == astate) {
		TRACE(" color mode %04x\n", cop->amode);
		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, cop->amode);
		amode = cop->amode;
	    }
	    else {
		TRACE(" color mode %04x\n", cop->cmode);
		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, cop->cmode);
	    }
	}
	TRACE(" %s: setting alpha mode %04x\n", GL_tex_op[astate].name, amode);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, amode);
	glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, aop->scale);
	D3D_GL_update_texture_factor(priv, cop, aop);
	D3D_GL_alpha_arg(priv, Stage, State);
	return STAGE_UPDATED;
    }
    return 0;
}

static inline DWORD D3D_GL_color_op(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    DWORD cstate = _TSS(Stage,COLOROP);
    DWORD astate = _TSS(Stage,ALPHAOP);
    GL_TEXOP *cop = D3D_GL_get_tex_op(priv, cstate, NULL);
    GL_TEXOP *aop = D3D_GL_get_tex_op(priv, astate, cop);
    _CTSS(Stage,COLOROP);
    priv->color_op[Stage] = cop;
    if (D3D_GL_have_tex_ext(priv, EXT_texture_env_combine)) {
	GLint cmode;
	priv->alpha_op[Stage] = aop;
	if (cstate <= D3DTOP_DISABLE) {
	    TRACE(" stage disable\n");
	    GL_StageDisabled(priv, Stage);
	    return 0;
	}
	if (!cop) {
	    TRACE(" unsupported\n");
	    D3D_GL_update_texture_factor(priv, cop, aop);
	    return 0;
	}
	if (cop->dwFlags & GLTF_NC) goto no_combiner;
	cmode = cop->cmode;
	if (cop->dwFlags & GLTF_SA) {
	    if (astate == cstate)
		cmode = cop->amode;
	}
	TRACE(" %s: setting color mode %04x\n", GL_tex_op[cstate].name, cmode);
	if (cop->ext == E_C4) {
	    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE4_NV);
	} else {
	    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
	}
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, cmode);
	glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, cop->scale);
	D3D_GL_update_texture_factor(priv, cop, aop);
	D3D_GL_color_arg(priv, Stage, State);
	/* reload alpha op too, in case TEXTURE_ENV_MODE was changed */
	D3D_GL_alpha_op(priv, Stage, State);
	/* stage is now enabled */
	GL_StageEnabled(priv, Stage);
	return 0; /* don't need STAGE_UPDATED when we did StageEnabled */
    }
    if (cstate == D3DTOP_DISABLE) {
	GL_StageDisabled(priv, Stage);
	return 0;
    }
    if (!cop) {
	TRACE(" unsupported\n");
	return 0;
    }
no_combiner:
    TRACE(" %s: setting mode %04x\n", GL_tex_op[cstate].name, cop->cmode);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, cop->cmode);
    D3D_GL_update_texture_factor(priv, cop, aop);
    GL_StageEnabled(priv, Stage);
    return 0; /* don't need STAGE_UPDATED when we did StageEnabled */
}

#undef COND_TEX_ENV

static inline DWORD D3D_GL_address_u(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _DWTSS(ADDRESSU);
    if (!target) return 0;
    glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_tex_addr[state]);
    return 0;
}

static inline DWORD D3D_GL_address_v(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _DWTSS(ADDRESSV);
    if (!target) return 0;
    glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_tex_addr[state]);
    return 0;
}

static inline DWORD D3D_GL_address_w(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _DWTSS(ADDRESSW);
    if (!target) return 0;
    glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_tex_addr[state]);
    return 0;
}

static inline DWORD D3D_GL_address(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _CTSS(Stage,ADDRESS);
    D3D_GL_address_u(priv, Stage, State);
    D3D_GL_address_w(priv, Stage, State);
    return 0;
}

static inline DWORD D3D_GL_border_color(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _COLTSS(BORDERCOLOR);
    if (!target) return 0;
    glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, state);
    return 0;
}

static inline DWORD D3D_GL_mag_filter(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _DWTSS(MAGFILTER);
    if (!target) return 0;
    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_mag[state]);
    /* in case this toggled anisotropic filtering, we'll have
     * to make sure the anisotropy factor gets reloaded */
    SET_DIRTY_TSS(&priv->state, Stage, D3DTSS_MAXANISOTROPY);
    return 0;
}

static inline DWORD D3D_GL_min_filter(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    DWORD minf = _TSS(Stage,MINFILTER);
    DWORD mipf = _TSS(Stage,MIPFILTER);
    _CTSS(Stage,MINFILTER); _CTSS(Stage,MIPFILTER);
    if (!target) return 0;
    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_mip_min[mipf][minf]);
    /* in case this toggled anisotropic filtering, we'll have
     * to make sure the anisotropy factor gets reloaded */
    SET_DIRTY_TSS(&priv->state, Stage, D3DTSS_MAXANISOTROPY);
    return 0;
}

static inline DWORD D3D_GL_max_mip_level(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _DWTSS(MAXMIPLEVEL);
    if (!target) return 0;
    glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, state);
    return 0;
}

static inline DWORD D3D_GL_max_anisotropy(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    DWORD magf = _TSS(Stage,MAGFILTER);
    DWORD minf = _TSS(Stage,MINFILTER);
    _DWTSS(MAXANISOTROPY);
    if (!target) return 0;
    if (!HAVE_GL_EXT(priv, EXT_texture_filter_anisotropic)) return 0;
    if (magf == D3DTFG_ANISOTROPIC && minf == D3DTFN_ANISOTROPIC)
	/* anisotropic filtering enabled */
	glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, state);
    else
	/* anisotropic filtering disabled */
	glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
    return 0;
}

static DWORD D3D_GL_mipmap_lod_bias(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _DVTSS(MIPMAPLODBIAS);
    /* this used to crash some Mesa/DRI versions, perhaps this extension
     * should be disabled (in caps.c) if such a version is in use. */
    if (!HAVE_GL_EXT(priv, EXT_texture_lod_bias)) return 0;
    glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, state);
    return 0;
}

static DWORD D3D_GL_tex_coord_index(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _DWTSS(TEXCOORDINDEX);
    priv->glFlags |= GLF_NEW_TEXCOORD_TRANSFORM;
    if ((state & D3DTSS_TCI_MASK) != 0)
	priv->texture_flags &= ~GLTF_DEFAULT_TEXCOORD_TRANSFORM;
    return 0;
}

static DWORD D3D_GL_tex_xform_flags(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    _DWTSS(TEXTURETRANSFORMFLAGS);
    priv->glFlags |= GLF_NEW_TEXCOORD_TRANSFORM;
    if (state != D3DTTFF_DISABLE)
	priv->texture_flags &= ~GLTF_DEFAULT_TEXCOORD_TRANSFORM;
    return 0;
}

static DWORD D3D_GL_texture_map(GL_D3D_priv* priv, DWORD Stage, DWORD State)
{
    GLenum target = priv->texture_target[Stage];
    _DWTSS(TEXTUREMAP);
    if (state) {
	LPD3D_T tex = (LPD3D_T)state;
	GLenum new_target;
	if (TCONTEXTLESS(tex)) {
	    HRESULT hr = GL_PrepTexture(priv, tex, tex);
	    if (FAILED(hr)) {
		ERR("texture load failed, result=%08lx\n", hr);
		return STAGE_UPDATED;
	    }
	}
	new_target = GL_tgt_par(tex);
	if (new_target != target) {
	    if (target) {
		glBindTexture(target, 0);
		glDisable(target);
		priv->texture_used[Stage] = FALSE;
	    }
	    target = new_target;
	    priv->texture_target[Stage] = target;
	}
	TRACE("binding texture ID %d\n", TEXTURE_ID(tex));
#ifdef WIREFRAME
    if (!gWireframe)
#endif
	glBindTexture(target, TEXTURE_ID(tex));

	/* these parameters are bound to the texture in OpenGL,
	 * but not in Direct3D versions before DirectX 9, so they
	 * must be reloaded for every texture change */
	D3D_GL_mag_filter(priv, Stage, State);
	D3D_GL_min_filter(priv, Stage, State);
	D3D_GL_address_u(priv, Stage, State);
	D3D_GL_address_v(priv, Stage, State);
	D3D_GL_address_w(priv, Stage, State);
	D3D_GL_border_color(priv, Stage, State);
	D3D_GL_max_mip_level(priv, Stage, State);
	D3D_GL_max_anisotropy(priv, Stage, State);
    } else {
	GLenum new_target = GL_TEXTURE_2D;
	if (new_target != target) {
	    if (target) {
		glBindTexture(target, 0);
		glDisable(target);
		priv->texture_used[Stage] = FALSE;
	    }
	    target = new_target;
	    priv->texture_target[Stage] = target;
	}
	TRACE("binding dummy texture ID %d\n", priv->dummy_texture);
#ifdef WIREFRAME
    if (!gWireframe)
#endif
	glBindTexture(target, priv->dummy_texture);
    }
    return STAGE_UPDATED;
}

static const struct {
    GL_texture_stage_state_handler func;
    const char*name;
} GL_texture_stage_state_func[64] = {
    {D3D_GL_texture_map,	"TEXTUREMAP"},
    {D3D_GL_color_op,		"COLOROP"},
    {D3D_GL_color_arg,		"COLORARG1"},
    {D3D_GL_color_arg,		"COLORARG2"},
    {D3D_GL_alpha_op,		"ALPHAOP"},
    {D3D_GL_alpha_arg,		"ALPHAARG1"},
    {D3D_GL_alpha_arg,		"ALPHAARG2"},
    {NULL,			"BUMPENVMAT00"},
    {NULL,			"BUMPENVMAT01"},
    {NULL,			"BUMPENVMAT10"},
    {NULL,			"BUMPENVMAT11"},
    {D3D_GL_tex_coord_index,	"TEXCOORDINDEX"},
    {D3D_GL_address,		"ADDRESS"},
    {D3D_GL_address_u,		"ADDRESSU"},
    {D3D_GL_address_v,		"ADDRESSV"},
    {D3D_GL_border_color,	"BORDERCOLOR"},
    {D3D_GL_mag_filter,		"MAGFILTER"},
    {D3D_GL_min_filter,		"MINFILTER"},
    {D3D_GL_min_filter,		"MIPFILTER"},
    {D3D_GL_mipmap_lod_bias,	"MIPMAPLODBIAS"},
    {D3D_GL_max_mip_level,	"MAXMIPLEVEL"},
    {D3D_GL_max_anisotropy,	"MAXANISOTROPY"},
    {NULL,			"BUMPENVLSCALE"},
    {NULL,			"BUMPENVLOFFSET"},
    /* DirectX 7 states */
    {D3D_GL_tex_xform_flags,	"TEXTURETRANSFORMFLAGS"},
    /* DirectX 8 states */
    {D3D_GL_address_w,		"ADDRESSW"},
    {D3D_GL_color_arg,		"COLORARG0"},
    {D3D_GL_alpha_arg,		"ALPHAARG0"},
    {D3D_GL_result_arg,		"RESULTARG"},
    {NULL,			NULL},
    {NULL,			NULL},
    {NULL,			NULL},
    /* DirectX 9 states */
    {NULL,			"CONSTANT"}
};

HRESULT D3D_GL_set_texture_stage_state(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2TEXTURESTAGESTATE dat = (LPD3DHAL_DP2TEXTURESTAGESTATE)data1;

    TRACE("stage %d, state %d (%s), value %08lx\n", dat->wStage, dat->TSState, GL_texture_stage_state_func[dat->TSState].name, dat->dwValue);

    if (dat->wStage >= priv->lpCaps->wMaxTextureBlendStages) {
	WARN("unsupported texture stage\n");
	return D3D_OK;
    }

    priv->block->texstagestate[dat->wStage][dat->TSState] = dat->dwValue;
    SET_DIRTY_TSS(priv->block, dat->wStage, dat->TSState);
    priv->block->dirty_ts |= 1 << dat->wStage;
    if (!GL_IN_BLOCK(priv))
	priv->glFlags |= GLF_NEW_TEXTURE_STATES;
    return D3D_OK;
}

HRESULT D3D_GL_update_texture_stage_states(GL_D3D_priv* priv)
{
    unsigned u, m, c;
    DWORD b;
    TRACE("\n");
    for (u=0; u<priv->lpCaps->wMaxTextureBlendStages; u++) if (priv->state.dirty_ts & (1 << u)) {
	DWORD flag = 0;
	GLD(priv)->glActiveTextureARB(GL_TEXTURE0_ARB + u);
	for (m=0; m<(64/32); m++) if (priv->state.dirty_tss[u][m]) {
	    c = m * 32;
	    b = 1;
	    while (b) {
		if (priv->state.dirty_tss[u][m] & b) {
		    TRACE("stage %d, state %d (%s), value 0x%08lx\n", u, c, GL_texture_stage_state_func[c].name, priv->state.texstagestate[u][c]);
		    if (!GL_texture_stage_state_func[c].func) {
			FIXME("unsupported texture stage state %d (%s), value=0x%08lx\n", c, GL_texture_stage_state_func[c].name, priv->state.texstagestate[u][c]);
			priv->state.dirty_tss[u][m] &= ~b;
			continue;
		    }
		    flag |= GL_texture_stage_state_func[c].func(priv, u, c);
		    CHECK_GL_ERROR("TextureStageState");
		}
		c++;
		b <<= 1;
	    }
	}
	if (flag & STAGE_UPDATED) GL_StageUpdated(priv, u);
    }
    priv->glFlags &= ~GLF_NEW_TEXTURE_STATES;
    return D3D_OK;
}

HRESULT WINAPI D3DGL_ValidateDevice(GL_D3D_priv* priv, LPDWORD lpdwPasses)
{
    unsigned u;
    HRESULT hr = D3D_OK;

    TRACE("(%p)->(%p)\n", priv, lpdwPasses);
    *lpdwPasses = 1;

    for (u=0; (!hr) && u < priv->lpCaps->wMaxTextureBlendStages &&
	      priv->state.texstagestate[u][D3DTSS_COLOROP] > D3DTOP_DISABLE; u++)
	hr = D3D_GL_validate_tex_stage(priv, u);

    TRACE("returning %ld\n", hr);
    return hr;
}
