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

#include <assert.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);

static const GLenum GL_D3DCMP[] = {
    0, GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER,
    GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS
};

static const GLint GL_D3DFOGMODE[4] = {
    0, GL_EXP, GL_EXP2, GL_LINEAR
};

/* 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 const GLenum GL_D3DBLEND[] = {
    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
};

static const GLenum GL_D3DBLENDOP[] = {
    0, GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX
};

static const GLenum GL_D3DSTENCILOP[] = {
    0, GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT,
    GL_INCR_WRAP_EXT, GL_DECR_WRAP_EXT
};

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;
}

#define D3DRS_ANTIALIAS			D3DRENDERSTATE_ANTIALIAS
#define D3DRS_TEXTUREPERSPECTIVE	D3DRENDERSTATE_TEXTUREPERSPECTIVE
#define D3DRS_EXTENTS			D3DRENDERSTATE_EXTENTS
#define D3DRS_ZBIAS			D3DRENDERSTATE_ZBIAS

#define _GRS(x) priv->state.renderstate[x]
#define _CLR(x) do { priv->state.dirty_rs[(x) / 32] &= ~(1 << ((x) & 31)); } while (0)
#define _RS(x) _GRS(D3DRS_##x)
#define _CRS(x) _CLR(D3DRS_##x)

#define _DWRS(x) DWORD state = _RS(x); _CRS(x)
#define _DVRS(x) float state = GL_RSF(_RS(x)); _CRS(x)
#define _COLRS(x) GLfloat state[4]; GL_RS2Col(state, _RS(x)); _CRS(x)

typedef void (*GL_render_state_handler)(GL_D3D_priv* priv, DWORD RenderState);

static void D3D_GL_fill_mode(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(FILLMODE);
#ifndef WIREFRAME
    switch (state) {
    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", state);
    }
#else
    if (!gWireframe)
    {
	switch (state) {
	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", state);
	}
    }
    else
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
}

static void D3D_GL_shade_mode(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(SHADEMODE);
    switch (state) {
    case D3DSHADE_FLAT:    glShadeModel(GL_FLAT); break;
    case D3DSHADE_GOURAUD: glShadeModel(GL_SMOOTH); break;
    default: FIXME("Unknown shading model: %ld\n", state);
    }
}

static void D3D_GL_cull_mode(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(CULLMODE);
    switch (state) {
    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", state);
    }
}

static void D3D_GL_anti_alias(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(ANTIALIAS);
    ((state == D3DANTIALIAS_SORTDEPENDENT) ? glEnable : glDisable)(GL_POLYGON_SMOOTH);
}

static void D3D_GL_z_write_enable(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(ZWRITEENABLE);
    glDepthMask(state);
}

static void D3D_GL_z_func(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(ZFUNC);
    glDepthFunc(GL_D3DCMP[state]);
}

static void D3D_GL_z_bias(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(ZBIAS);
    glPolygonOffset(0, -(GLfloat)state);
    (state ? glEnable : glDisable)(GL_POLYGON_OFFSET_FILL);
}

static void D3D_GL_last_pixel(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(LASTPIXEL);
    if (!state) {
	/* how to do this? */
	WARN("disable last pixel\n");
    }
}

static void D3D_GL_blend_func(GL_D3D_priv* priv, DWORD RenderState)
{
    DWORD src = _RS(SRCBLEND);
    DWORD dst = _RS(DESTBLEND);
    _CRS(SRCBLEND); _CRS(DESTBLEND);
    glBlendFunc(GL_D3DBLEND[src], GL_D3DBLEND[dst]);
}

static void D3D_GL_blend_op(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(BLENDOP);
    if (HAVE_GL_EXT(priv, ARB_imaging))
	glBlendEquation(GL_D3DBLENDOP[state]);
}

static void D3D_GL_alpha_func(GL_D3D_priv* priv, DWORD RenderState)
{
    DWORD ref = _RS(ALPHAREF);
    DWORD func = _RS(ALPHAFUNC);
    _CRS(ALPHAREF); _CRS(ALPHAFUNC);
    glAlphaFunc(GL_D3DCMP[func], (ref & 0xFF) / 255.0);
}

static void D3D_GL_stencil_op(GL_D3D_priv* priv, DWORD RenderState)
{
    DWORD fail = _RS(STENCILFAIL);
    DWORD zfail = _RS(STENCILZFAIL);
    DWORD pass = _RS(STENCILPASS);
    _CRS(STENCILFAIL); _CRS(STENCILZFAIL); _CRS(STENCILPASS);
    glStencilOp(GL_D3DSTENCILOP[fail], GL_D3DSTENCILOP[zfail], GL_D3DSTENCILOP[pass]);
}

static void D3D_GL_stencil_func(GL_D3D_priv* priv, DWORD RenderState)
{
    DWORD func = _RS(STENCILFUNC);
    DWORD ref = _RS(STENCILREF);
    DWORD mask = _RS(STENCILMASK);
    _CRS(STENCILFUNC); _CRS(STENCILREF); _CRS(STENCILMASK);
    glStencilFunc(GL_D3DCMP[func], ref, mask);
}

static void D3D_GL_stencil_mask(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(STENCILWRITEMASK);
    glStencilMask(state);
}

static void D3D_GL_color_mask(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(COLORWRITEENABLE);
    glColorMask((state & D3DCOLORWRITEENABLE_RED)   != 0,
		(state & D3DCOLORWRITEENABLE_GREEN) != 0,
		(state & D3DCOLORWRITEENABLE_BLUE)  != 0,
		(state & D3DCOLORWRITEENABLE_ALPHA) != 0);
}

static void D3D_GL_specular_enable(GL_D3D_priv* priv, DWORD RenderState)
{
    _CRS(SPECULARENABLE);
    priv->glFlags |= GLMF_SPECULAR_DIRTY;
}

static void D3D_GL_fog_enable(GL_D3D_priv* priv, DWORD RenderState)
{
    DWORD fog = _RS(FOGENABLE);
    DWORD tablemode = _RS(FOGTABLEMODE);
    DWORD vertexmode = _RS(FOGVERTEXMODE);
    DWORD rangefog = _RS(RANGEFOGENABLE);
    _CRS(FOGENABLE); _CRS(FOGTABLEMODE); _CRS(FOGVERTEXMODE); _CRS(RANGEFOGENABLE);
    if (priv->isFogLinear) return;
    if (fog) {
	if (tablemode) {
	    TRACE("enabling table fog\n");
	    if (!priv->isFogEnabled) {
		glEnable(GL_FOG);
		priv->isFogEnabled = TRUE;
	    }
	    glHint(GL_FOG_HINT, GL_NICEST);
	    glFogi(GL_FOG_MODE, GL_D3DFOGMODE[tablemode]);
	    /* "Because no hardware currently supports per-pixel range-based fog, range correction is offered only for vertex fog." */
	    if (HAVE_GL_EXT(priv, NV_fog_distance))
		glFogi(GL_FOG_DISTANCE_MODE_NV, GL_EYE_PLANE_ABSOLUTE_NV);
	}
	else if (vertexmode) {
	    TRACE("enabling vertex fog\n");
	    if (!priv->isFogEnabled) {
		glEnable(GL_FOG);
		priv->isFogEnabled = TRUE;
	    }
	    glHint(GL_FOG_HINT, GL_FASTEST);
	    glFogi(GL_FOG_MODE, GL_D3DFOGMODE[vertexmode]);
	    if (HAVE_GL_EXT(priv, NV_fog_distance))
		glFogi(GL_FOG_DISTANCE_MODE_NV, rangefog ? GL_EYE_RADIAL_NV : GL_EYE_PLANE_ABSOLUTE_NV);
	}
	else if (priv->isFogEnabled) {
	    glDisable(GL_FOG);
	    priv->isFogEnabled = FALSE;
	}
    }
    else if (priv->isFogEnabled) {
	glDisable(GL_FOG);
	priv->isFogEnabled = FALSE;
    }
}

void GL_setup_linear_fog(GL_D3D_priv* priv, BOOL linear, BOOL enable)
{
    if (linear) {
	if (enable) {
	    if (!priv->isFogEnabled) {
		glEnable(GL_FOG);
		priv->isFogEnabled = TRUE;
	    }
	    if (!priv->isFogLinear) {
		glHint(GL_FOG_HINT, GL_FASTEST);
		glFogi(GL_FOG_MODE, GL_LINEAR);
		if (HAVE_GL_EXT(priv, NV_fog_distance))
		    glFogi(GL_FOG_DISTANCE_MODE_NV, GL_EYE_PLANE_ABSOLUTE_NV);
		glFogf(GL_FOG_START, 1.0);
		glFogf(GL_FOG_END, 0.0);
		glFogf(GL_FOG_DENSITY, 0.0);
		priv->isFogLinear = TRUE;
	    }
	}
	else if (priv->isFogEnabled) {
	    glDisable(GL_FOG);
	    priv->isFogEnabled = FALSE;
	}
    }
    else if (priv->isFogLinear) {
	priv->isFogLinear = FALSE;
	D3D_GL_fog_enable(priv, 0);
	/* reload stuff previously suppressed by isFogLinear in D3D_GL_fog */
	glFogf(GL_FOG_START, GL_RSF(_RS(FOGSTART)));
	glFogf(GL_FOG_END, GL_RSF(_RS(FOGEND)));
	glFogf(GL_FOG_DENSITY, GL_RSF(_RS(FOGDENSITY)));
    }
    else if (!priv->isFogEnabled) {
	/* re-enable fog if we need to */
	D3D_GL_fog_enable(priv, 0);
    }
}

static void D3D_GL_fog_color(GL_D3D_priv* priv, DWORD RenderState)
{
    _COLRS(FOGCOLOR);
    glFogfv(GL_FOG_COLOR, state);
}

static void D3D_GL_ambient(GL_D3D_priv* priv, DWORD RenderState)
{
    _COLRS(AMBIENT);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, state);
}

static void D3D_GL_local_viewer(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(LOCALVIEWER);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, state);
}

static void D3D_GL_normalize_normals(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(NORMALIZENORMALS);
    (state ? glEnable : glDisable)(GL_NORMALIZE);
    /* (state ? glDisable : glEnable)(GL_RESCALE_NORMAL); */
}

static void D3D_GL_lighting(GL_D3D_priv* priv, DWORD RenderState)
{
    _CRS(LIGHTING);
    priv->glFlags |= GLF_NEW_LIGHTING;
}

static void D3D_GL_color_vertex(GL_D3D_priv* priv, DWORD RenderState)
{
    _CRS(COLORVERTEX);
    _CRS(DIFFUSEMATERIALSOURCE);
    _CRS(SPECULARMATERIALSOURCE);
    _CRS(AMBIENTMATERIALSOURCE);
    _CRS(EMISSIVEMATERIALSOURCE);
    priv->glFlags |= GLF_NEW_LIGHTING;
}

static void D3D_GL_vertex_blend(GL_D3D_priv* priv, DWORD RenderState)
{
    _CRS(VERTEXBLEND);
    priv->glFlags |= GLF_NEW_TNL_MODE;
}

static void D3D_GL_texture_factor(GL_D3D_priv* priv, DWORD RenderState)
{
    _COLRS(TEXTUREFACTOR);
    GL_LoadTextureFactor(priv, state);
}

static void D3D_GL_texture_perspective(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(TEXTUREPERSPECTIVE);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, state ? GL_NICEST : GL_FASTEST);
}

static void D3D_GL_clipping(GL_D3D_priv* priv, DWORD RenderState)
{
    _DWRS(CLIPPING);
    if (HAVE_GL_EXT(priv, EXT_clip_volume_hint))
	glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, state ? GL_NICEST : GL_FASTEST);
}

static void D3D_GL_clip_plane_enable(GL_D3D_priv* priv, DWORD RenderState)
{
    unsigned u;
    DWORD enabled, enable;
    _DWRS(CLIPPLANEENABLE);
    enabled = priv->isClipPlaneEnabled;
    enable = state ^ enabled;
    for (u=0; u<priv->lpCaps->wMaxUserClipPlanes; u++)
	if (enable & (1 << u)) {
	    if (state & (1 << u))
		glEnable(GL_CLIP_PLANE0 + u);
	    else
		glDisable(GL_CLIP_PLANE0 + u);
	}
    priv->isClipPlaneEnabled = state;
}

static void D3D_GL_extents(GL_D3D_priv* priv, DWORD RenderState)
{
    _CRS(EXTENTS);
}

static void D3D_GL_enable(GL_D3D_priv* priv, DWORD RenderState);
static void D3D_GL_fog(GL_D3D_priv* priv, DWORD RenderState);

static const struct {
    GL_render_state_handler func;
    GLenum tok;
    const char*name;
} GL_render_state_func[256] = {
    {NULL},
    {NULL, 0,						"TEXTUREHANDLE"}, /* obsolete */
    {D3D_GL_anti_alias, 0,				"ANTIALIAS"},
    {NULL, 0,						"TEXTUREADDRESS"}, /* obsolete */
    {D3D_GL_texture_perspective, 0,			"TEXTUREPERSPECTIVE"},
    {NULL, 0,						"WRAPU"}, /* obsolete */
    {NULL, 0,						"WRAPV"}, /* obsolete */
    {D3D_GL_enable, GL_DEPTH_TEST,			"ZENABLE"},
    {D3D_GL_fill_mode, 0,				"FILLMODE"},
    {D3D_GL_shade_mode, 0,				"SHADEMODE"},
    {NULL, 0,						"LINEPATTERN"},
    {NULL, 0,						"MONOENABLE"},
    {NULL, 0,						"ROP2"},
    {NULL, 0,						"PLANEMASK"},
    {D3D_GL_z_write_enable, 0,				"ZWRITEENABLE"},
    {D3D_GL_enable, GL_ALPHA_TEST,			"ALPHATESTENABLE"},
    {D3D_GL_last_pixel, 0,				"LASTPIXEL"},
    {NULL, 0,						"TEXTUREMAG"}, /* obsolete */
    {NULL, 0,						"TEXTUREMIN"}, /* obsolete */
    {D3D_GL_blend_func, 0,				"SRCBLEND"},
    {D3D_GL_blend_func, 0,				"DESTBLEND"},
    {NULL, 0,						"TEXTUREMAPBLEND"}, /* obsolete */
    {D3D_GL_cull_mode, 0,				"CULLMODE"},
    {D3D_GL_z_func, 0,					"ZFUNC"},
    {D3D_GL_alpha_func, 0,				"ALPHAREF"},
    {D3D_GL_alpha_func, 0,				"ALPHAFUNC"},
    {D3D_GL_enable, GL_DITHER,				"DITHERENABLE"},
    {D3D_GL_enable, GL_BLEND,				"ALPHABLENDENABLE"},
    {D3D_GL_fog_enable, 0,				"FOGENABLE"},
    {D3D_GL_specular_enable, 0,				"SPECULARENABLE"},
    {NULL, 0,						"ZVISIBLE"},
    {NULL, 0,						"SUBPIXEL"},
    {NULL, 0,						"SUBPIXELX"},
    {NULL, 0,						"STIPPLEDALPHA"},
    {D3D_GL_fog_color, 0,				"FOGCOLOR"},
    {D3D_GL_fog_enable, 0,				"FOGTABLEMODE"},
    {D3D_GL_fog, GL_FOG_START,				"FOGSTART"},
    {D3D_GL_fog, GL_FOG_END,				"FOGEND"},
    {D3D_GL_fog, GL_FOG_DENSITY,			"FOGDENSITY"},
    {NULL, 0,						"STIPPLEENABLE"},
    {NULL, 0,						"EDGEANTIALIAS"},
    {NULL, 0,						"COLORKEYENABLE"},
    {NULL, 0,						"OLDALPHABLENDENABLE"}, /* obsolete */
    {NULL, 0,						"BORDERCOLOR"}, /* obsolete */
    {NULL, 0,						"TEXTUREADDRESSU"}, /* obsolete */
    {NULL, 0,						"TEXTUREADDRESSV"}, /* obsolete */
    {NULL, 0,						"MIPMAPLODBIAS"}, /* obsolete */
    {D3D_GL_z_bias, 0,					"ZBIAS"},
    {D3D_GL_fog_enable, 0,				"RANGEFOGENABLE"},
    {NULL, 0,						"ANISOTROPY"}, /* obsolete */
    {NULL, 0,						"FLUSHBATCH"}, /* obsolete */
    {NULL, 0,						"TRANSLUCENTSORTINDEPENDENT"},
    {D3D_GL_enable, GL_STENCIL_TEST,			"STENCILENABLE"},
    {D3D_GL_stencil_op, 0,				"STENCILFAIL"},
    {D3D_GL_stencil_op, 0,				"STENCILZFAIL"},
    {D3D_GL_stencil_op, 0,				"STENCILPASS"},
    {D3D_GL_stencil_func, 0,				"STENCILFUNC"},
    {D3D_GL_stencil_func, 0,				"STENCILREF"},
    {D3D_GL_stencil_func, 0,				"STENCILMASK"},
    {D3D_GL_stencil_mask, 0,				"STENCILWRITEMASK"},
    {D3D_GL_texture_factor, 0,				"TEXTUREFACTOR"},
    {NULL}, {NULL}, {NULL},
    {NULL, 0,						"STIPPLEPATTERN00"},
    {NULL, 0,						"STIPPLEPATTERN01"},
    {NULL, 0,						"STIPPLEPATTERN02"},
    {NULL, 0,						"STIPPLEPATTERN03"},
    {NULL, 0,						"STIPPLEPATTERN04"},
    {NULL, 0,						"STIPPLEPATTERN05"},
    {NULL, 0,						"STIPPLEPATTERN06"},
    {NULL, 0,						"STIPPLEPATTERN07"},
    {NULL, 0,						"STIPPLEPATTERN08"},
    {NULL, 0,						"STIPPLEPATTERN09"},
    {NULL, 0,						"STIPPLEPATTERN10"},
    {NULL, 0,						"STIPPLEPATTERN11"},
    {NULL, 0,						"STIPPLEPATTERN12"},
    {NULL, 0,						"STIPPLEPATTERN13"},
    {NULL, 0,						"STIPPLEPATTERN14"},
    {NULL, 0,						"STIPPLEPATTERN15"},
    {NULL, 0,						"STIPPLEPATTERN16"},
    {NULL, 0,						"STIPPLEPATTERN17"},
    {NULL, 0,						"STIPPLEPATTERN18"},
    {NULL, 0,						"STIPPLEPATTERN19"},
    {NULL, 0,						"STIPPLEPATTERN20"},
    {NULL, 0,						"STIPPLEPATTERN21"},
    {NULL, 0,						"STIPPLEPATTERN22"},
    {NULL, 0,						"STIPPLEPATTERN23"},
    {NULL, 0,						"STIPPLEPATTERN24"},
    {NULL, 0,						"STIPPLEPATTERN25"},
    {NULL, 0,						"STIPPLEPATTERN26"},
    {NULL, 0,						"STIPPLEPATTERN27"},
    {NULL, 0,						"STIPPLEPATTERN28"},
    {NULL, 0,						"STIPPLEPATTERN29"},
    {NULL, 0,						"STIPPLEPATTERN30"},
    {NULL, 0,						"STIPPLEPATTERN31"},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL, 0,						"WRAP0"},
    {NULL, 0,						"WRAP1"},
    {NULL, 0,						"WRAP2"},
    {NULL, 0,						"WRAP3"},
    {NULL, 0,						"WRAP4"},
    {NULL, 0,						"WRAP5"},
    {NULL, 0,						"WRAP6"},
    {NULL, 0,						"WRAP7"},
    /* DirectX 7 states */
    {D3D_GL_clipping, 0,				"CLIPPING"},
    {D3D_GL_lighting, 0,				"LIGHTING"},
    {D3D_GL_extents, 0,					"EXTENTS"},
    {D3D_GL_ambient, 0,					"AMBIENT"},
    {D3D_GL_fog_enable, 0,				"FOGVERTEXMODE"},
    {D3D_GL_color_vertex, 0,				"COLORVERTEX"},
    {D3D_GL_local_viewer, 0,				"LOCALVIEWER"},
    {D3D_GL_normalize_normals, 0,			"NORMALIZENORMALS"},
    {NULL, 0,						"COLORKEYBLENDENABLE"},
    {D3D_GL_color_vertex, 0,				"DIFFUSEMATERIALSOURCE"},
    {D3D_GL_color_vertex, 0,				"SPECULARMATERIALSOURCE"},
    {D3D_GL_color_vertex, 0,				"AMBIENTMATERIALSOURCE"},
    {D3D_GL_color_vertex, 0,				"EMISSIVEMATERIALSOURCE"},
    {NULL}, {NULL},
    {D3D_GL_vertex_blend, 0,				"VERTEXBLEND"},
    {D3D_GL_clip_plane_enable, 0,			"CLIPPLANEENABLE"},
    /* DirectX 8 states */
    {NULL, 0,						"SOFTWAREVERTEXPROCESSING"},
    {NULL, 0,						"POINTSIZE"},
    {NULL, 0,						"POINTSIZE_MIN"},
    {NULL, 0,						"POINTSPRITEENABLE"},
    {NULL, 0,						"POINTSCALEENABLE"},
    {NULL, 0,						"POINTSCALE_A"},
    {NULL, 0,						"POINTSCALE_B"},
    {NULL, 0,						"POINTSCALE_C"},
    {NULL, 0,						"MULTISAMPLEANTIALIAS"},
    {NULL, 0,						"MULTISAMPLEMASK"},
    {NULL, 0,						"PATCHEDGESTYLE"},
    {NULL, 0,						"PATCHSEGMENTS"},
    {NULL, 0,						"DEBUGMONITORTOKEN"},
    {NULL, 0,						"POINTSIZE_MAX"},
    {NULL, 0,						"INDEXEDVERTEXBLENDENABLE"},
    {D3D_GL_color_mask, 0,				"COLORWRITEENABLE"},
    {NULL, 0,						NULL},
    {NULL, 0,						"TWEENFACTOR"},
    {D3D_GL_blend_op, 0,				"BLENDOP"},
    {NULL, 0,						"POSITIONORDER"},
    {NULL, 0,						"NORMALORDER"}, /* 173 */
    /* DirectX 9 states */
    {NULL, 0,						"SCISSORTESTENABLE"},
    {NULL, 0,						"SLOPESCALEDEPTHBIAS"},
    {NULL, 0,						"ANTIALIASEDLINEENABLE"},
    {NULL}, /* 177 */
    {NULL, 0,						"MINTESSELLATIONLEVEL"},
    {NULL, 0,						"MAXTESSELLATIONLEVEL"},
    {NULL, 0,						"ADAPTIVETESS_X"},
    {NULL, 0,						"ADAPTIVETESS_Y"},
    {NULL, 0,						"ADAPTIVETESS_Z"},
    {NULL, 0,						"ADAPTIVETESS_W"},
    {NULL, 0,						"ENABLEADAPTIVETESSELLATION"},
    {NULL, 0,						"TWOSIDEDSTENCILMODE"},
    {NULL, 0,						"CCW_STENCILFAIL"},
    {NULL, 0,						"CCW_STENCILZFAIL"},
    {NULL, 0,						"CCW_STENCILPASS"},
    {NULL, 0,						"CCW_STENCILFUNC"},
    {NULL, 0,						"COLORWRITEENABLE1"},
    {NULL, 0,						"COLORWRITEENABLE2"},
    {NULL, 0,						"COLORWRITEENABLE3"},
    {NULL, 0,						"BLENDFACTOR"},
    {NULL, 0,						"SRGBWRITEENABLE"},
    {NULL, 0,						"DEPTHBIAS"},
    {NULL}, {NULL},
    {NULL, 0,						"WRAP8"},
    {NULL, 0,						"WRAP9"},
    {NULL, 0,						"WRAP10"},
    {NULL, 0,						"WRAP11"},
    {NULL, 0,						"WRAP12"},
    {NULL, 0,						"WRAP13"},
    {NULL, 0,						"WRAP14"},
    {NULL, 0,						"WRAP15"},
    {NULL, 0,						"SEPARATEALPHABLENDENABLE"},
    {NULL, 0,						"SRCBLENDALPHA"},
    {NULL, 0,						"DESTBLENDALPHA"},
    {NULL, 0,						"BLENDOPALPHA"},
    {NULL}
};

static void D3D_GL_enable(GL_D3D_priv* priv, DWORD RenderState)
{
    DWORD state = _GRS(RenderState); _CLR(RenderState);
    (state ? glEnable : glDisable)(GL_render_state_func[RenderState].tok);
}

static void D3D_GL_fog(GL_D3D_priv* priv, DWORD RenderState)
{
    float state = GL_RSF(_GRS(RenderState)); _CLR(RenderState);
    if (priv->isFogLinear) return;
    glFogf(GL_render_state_func[RenderState].tok, state);
}

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

    TRACE("state %d (%s), value 0x%08lx\n", dat->RenderState, GL_render_state_func[dat->RenderState].name, dat->u.dwState);

    /* FIXME: here we can check render state for validity */

    priv->block->renderstate[dat->RenderState] = dat->u.dwState;
    SET_DIRTY_RS(priv->block, dat->RenderState);
    if (!GL_IN_BLOCK(priv)) {
	priv->glFlags |= GLF_NEW_RENDER_STATES;

	/* needed by high-level d3d implementation */
	priv->d3dp->renderstate[dat->RenderState] = dat->u.dwState;
    }

    return D3D_OK;
}

HRESULT D3D_GL_update_render_states(GL_D3D_priv* priv)
{
    unsigned m, c;
    DWORD b;
    TRACE("\n");
    for (m=0; m<(256/32); m++) if (priv->state.dirty_rs[m]) {
	c = m * 32;
	b = 1;
	while (b) {
	    if (priv->state.dirty_rs[m] & b) {
		TRACE("state %d (%s), value 0x%08lx\n", c, GL_render_state_func[c].name, priv->state.renderstate[c]);
		if (!GL_render_state_func[c].func) {
		    FIXME("unsupported render state %d (%s), value=0x%08lx\n", c, GL_render_state_func[c].name, priv->state.renderstate[c]);
		    priv->state.dirty_rs[m] &= ~b;
		    continue;
		}
		GL_render_state_func[c].func(priv, c);
		CHECK_GL_ERROR("RenderState");
	    }
	    c++;
	    b <<= 1;
	}
    }
    priv->glFlags &= ~GLF_NEW_RENDER_STATES;
    return D3D_OK;
}

HRESULT D3D_GL_viewport_info(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2VIEWPORTINFO dat = (LPD3DHAL_DP2VIEWPORTINFO)data1;
    TRACE("X: %ld, Y: %ld, Width: %ld, Height: %ld\n",
	  dat->dwX, dat->dwY, dat->dwWidth, dat->dwHeight);
    priv->block->viewport.dwX = dat->dwX;
    priv->block->viewport.dwY = dat->dwY;
    priv->block->viewport.dwWidth  = dat->dwWidth;
    priv->block->viewport.dwHeight = dat->dwHeight;
    priv->block->dirty_viewport = TRUE;
    if (!GL_IN_BLOCK(priv)) {
	priv->glFlags |= GLF_NEW_TRANSFORM;

	priv->d3dp->viewport.dwX = dat->dwX;
	priv->d3dp->viewport.dwY = dat->dwY;
	priv->d3dp->viewport.dwWidth  = dat->dwWidth;
	priv->d3dp->viewport.dwHeight = dat->dwHeight;
    }
    return D3D_OK;
}

HRESULT D3D_GL_z_range(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2ZRANGE dat = (LPD3DHAL_DP2ZRANGE)data1;
    TRACE("Zmin: %f, Zmax: %f\n", dat->dvMinZ, dat->dvMaxZ);
    priv->block->viewport.dvMinZ = dat->dvMinZ;
    priv->block->viewport.dvMaxZ = dat->dvMaxZ;
    priv->block->dirty_viewport = TRUE;
    if (!GL_IN_BLOCK(priv)) {
	priv->glFlags |= GLF_NEW_TRANSFORM;

	priv->d3dp->viewport.dvMinZ = dat->dvMinZ;
	priv->d3dp->viewport.dvMaxZ = dat->dvMaxZ;
    }
    return D3D_OK;
}

HRESULT D3D_GL_update_viewport(GL_D3D_priv* priv)
{
    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. */
#ifndef HAVE_OSMESA
    if (!(priv->glFlags & GLF_RENDER_INVERTED))
#else
    if (priv->glFlags & GLF_RENDER_INVERTED)
#endif
	gl_y = priv->d3dp->surf->lpGbl->wHeight - (priv->state.viewport.dwY + priv->state.viewport.dwHeight);
    else
	gl_y = priv->state.viewport.dwY;

    glScissor(priv->state.viewport.dwX, gl_y, priv->state.viewport.dwWidth, priv->state.viewport.dwHeight);
    glViewport(priv->state.viewport.dwX, gl_y, priv->state.viewport.dwWidth, priv->state.viewport.dwHeight);
    glDepthRange(priv->state.viewport.dvMinZ, priv->state.viewport.dvMaxZ);
    priv->state.dirty_viewport = FALSE;

    return D3D_OK;
}

HRESULT D3D_GL_set_current_material(GL_D3D_priv* priv, LPD3DMATERIAL7 mat)
{
    TRACE("%p\n", mat);

    if (memcmp(&priv->state.material.u.diffuse, &mat->u.diffuse, sizeof(D3DCOLORVALUE)))
    {
	memcpy(&priv->state.material.u.diffuse, &mat->u.diffuse, sizeof(D3DCOLORVALUE));
	priv->material_flags |= GLMF_DIFFUSE_DIRTY;
    }

    if (memcmp(&priv->state.material.u1.ambient, &mat->u1.ambient, sizeof(D3DCOLORVALUE)))
    {
	memcpy(&priv->state.material.u1.ambient, &mat->u1.ambient, sizeof(D3DCOLORVALUE));
	priv->material_flags |= GLMF_AMBIENT_DIRTY;
    }

    if (memcmp(&priv->state.material.u2.specular, &mat->u2.specular, sizeof(D3DCOLORVALUE)))
    {
	memcpy(&priv->state.material.u2.specular, &mat->u2.specular, sizeof(D3DCOLORVALUE));
	priv->material_flags |= GLMF_SPECULAR_DIRTY;
    }

    if (memcmp(&priv->state.material.u3.emissive, &mat->u3.emissive, sizeof(D3DCOLORVALUE)))
    {
	memcpy(&priv->state.material.u3.emissive, &mat->u3.emissive, sizeof(D3DCOLORVALUE));
	priv->material_flags |= GLMF_EMISSIVE_DIRTY;
    }

    if (priv->state.material.u4.power != mat->u4.power)
    {
	priv->state.material.u4.power = mat->u4.power;
	priv->material_flags |= GLMF_SHININESS_DIRTY;
    }

    priv->state.dirty_material = TRUE;

    return D3D_OK;
}

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

    if (GL_IN_BLOCK(priv)) {
	priv->block->material = *dat;
	priv->block->dirty_material = TRUE;
    } else {
	D3D_GL_set_current_material(priv, dat);
	priv->d3dp->material = priv->state.material;
    }
    return D3D_OK;
}

HRESULT D3D_GL_set_transform(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2SETTRANSFORM dat = (LPD3DHAL_DP2SETTRANSFORM)data1;
    unsigned u;

    TRACE("type=%d\n", dat->xfrmType);
    if (!dat->xfrmType) {
	priv->glFlags |= GLF_NEW_TRANSFORM;
	if (!cmd->bReserved) {
	    priv->glFlags &= ~GLF_USE_CLIPMATRIX;
	    return D3D_OK;
	}
	priv->glFlags |= GLF_USE_CLIPMATRIX;
    }
    for (u=0; u<4; u++) {
	TRACE(" %8.5f %8.5f %8.5f %8.5f\n",
	      dat->matrix.u.m[u][0],
	      dat->matrix.u.m[u][1],
	      dat->matrix.u.m[u][2],
	      dat->matrix.u.m[u][3]);
    }

    if (dat->xfrmType >= D3DTRANSFORMSTATE_TEXTURE0 &&
	dat->xfrmType <= D3DTRANSFORMSTATE_TEXTURE7)
    {
	u = dat->xfrmType - D3DTRANSFORMSTATE_TEXTURE0;
	priv->block->txfrm[u] = dat->matrix;
	priv->block->dirty_txfrm |= 1 << u;
	if (!GL_IN_BLOCK(priv)) {
	    priv->glFlags |= GLF_NEW_TEXCOORD_TRANSFORM;
	    priv->texture_flags &= ~GLTF_DEFAULT_TEXCOORD_TRANSFORM;
	}
    }
    else if (dat->xfrmType >= D3DTRANSFORMSTATE_WORLD &&
	     dat->xfrmType <= D3DTRANSFORMSTATE_WORLD3)
    {
	u = dat->xfrmType;
	priv->block->xfrm[u] = dat->matrix;
	priv->block->dirty_xfrm |= 1 << u;
	if (!GL_IN_BLOCK(priv)) {
	    priv->glFlags |= GLF_NEW_TRANSFORM;
	    if (dat->xfrmType == D3DTRANSFORMSTATE_VIEW) priv->glFlags |= GLF_NEW_VIEW;
	}
    }

    /* the real work is done in glprim.c */
    return D3D_OK;
}

HRESULT D3D_GL_set_clip_plane(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2SETCLIPPLANE dat = (LPD3DHAL_DP2SETCLIPPLANE)data1;
    unsigned u;

    for (u=0; u<4; u++)
	priv->block->clipplane[dat->dwIndex][u] = dat->plane[u];
    priv->block->dirty_clip_planes |= 1 << dat->dwIndex;
    if (!GL_IN_BLOCK(priv))
	priv->glFlags |= GLF_NEW_CLIP_PLANES;
    return D3D_OK;
}

HRESULT D3D_GL_update_clip_planes(GL_D3D_priv* priv, BOOL new_view, BOOL fixed)
{
    DWORD state = _RS(CLIPPLANEENABLE);
    DWORD dirty = (new_view ? (DWORD)-1 : priv->state.dirty_clip_planes) & state;
    unsigned u;
    if (!dirty) return D3D_OK;
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    if (fixed) glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
    else glLoadIdentity();
    for (u=0; u<priv->lpCaps->wMaxUserClipPlanes; u++)
	if (dirty & (1 << u)) {
	    GLdouble plane[4];
	    plane[0] = priv->state.clipplane[u][0];
	    plane[1] = priv->state.clipplane[u][1];
	    plane[2] = priv->state.clipplane[u][2];
	    plane[3] = priv->state.clipplane[u][3];
	    glClipPlane(GL_CLIP_PLANE0 + u, plane);
	}
    glPopMatrix();
    priv->state.dirty_clip_planes &= ~dirty;
    priv->glFlags &= ~GLF_NEW_CLIP_PLANES;
    return D3D_OK;
}
