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

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

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

/* #define DEBUG_VERTEX_DATA */

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

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

/* stuff we use that may be missing from glext.h */
#ifndef GL_ARB_texture_cube_map
#define GL_NORMAL_MAP_ARB	0x8511
#define GL_REFLECTION_MAP_ARB	0x8512
#endif

#define D3DFVF_TEXCOUNT(x) (((x) & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT)
#define D3DFVF_TEXCOORDSIZE(fvf,idx) ( ((((fvf)>>((idx)*2 + 16)) + 1) & 0x0003) + 1 )

/* D3DFVF_XYZRHW and D3DFVF_XYZGLW */
#define ISTLFVF(x) (((x) & 0xe) == 4)

static inline DWORD D3D_GL_PositionSize(DWORD fvf)
{
    static const DWORD position_size[] = { 0, 3, 4, 4, 5, 6, 7, 8 };
    return position_size[(fvf & 0xe) / 2];
}

static inline DWORD D3D_GL_BlendWeights(DWORD fvf)
{
    return max((fvf & 0xe) / 2, 2) - 2;
}

static inline void GL_convert_position(const float* in, float* out)
{
    float w;

    /* Reportedly Microsoft uses 2^20 in their ProcessVertices impl. */
    if (in[3] == 0.0) w = 1048576.0;
    else w = 1.0 / in[3];

    out[0] = in[0] * w;
    out[1] = in[1] * w;
    out[2] = in[2] * w;
    out[3] = w;
}


static HRESULT GL_build_position_array(GL_D3D_priv* priv, DWORD fvf,
				       DWORD vstart, DWORD vcount,
				       LPGLDRAWPRIMITIVESTRIDEDDATA data,
				       const WORD* indices, DWORD nindices,
				       DWORD ibase)
{
    LPBYTE in;
    LPD3DVALUE out;
    DWORD stride = data->position.dwStride;
    DWORD size;

    size = vcount * 4 * sizeof(D3DVALUE);
    if (size > priv->vertex_array_size)
    {
	LPVOID new_array;

	size = max(size, 2 * priv->vertex_array_size);

	new_array = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
				 PAGE_READWRITE);

	if (new_array == NULL) return E_OUTOFMEMORY;

	VirtualFree(priv->vertex_array, 0, MEM_RELEASE);

	priv->vertex_array = new_array;
	priv->vertex_array_size = size;
    }

    in = data->position.lpvData;
    out = priv->vertex_array;

    if (indices != NULL && nindices < vcount)
    {
	in += ibase * stride;
	out -= (vstart - ibase) * 4;
	while (nindices--)
	{
	    DWORD index = *indices++;

	    GL_convert_position((LPFLOAT)(in + index * stride),
				(LPFLOAT)(out + index * 4));
	}
    }
    else
    {
	in += vstart * stride;
	while (vcount--)
	{
	    GL_convert_position((LPFLOAT)in, (LPFLOAT)out);

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

    return DD_OK;
}

static void GL_fixup_position_array(GL_D3D_priv* priv, DWORD fvf,
				    DWORD vstart, DWORD vcount,
				    LPGLDRAWPRIMITIVESTRIDEDDATA data,
				    const WORD* indices, DWORD nindices,
				    DWORD ibase)
{
    if ((fvf & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW)
    {
	DWORD stride = 4 * sizeof(D3DVALUE);

	GL_build_position_array(priv, fvf, vstart, vcount, data, indices, nindices, ibase);

	data->position.lpvData = (LPBYTE)priv->vertex_array - vstart * stride;
	data->position.dwStride = stride;
    }
}


static inline DWORD rot16(DWORD x)
{
    return (x << 16) | (x >> 16);
}

static inline D3DCOLOR GL_reorder_color(D3DCOLOR x)
{
    return (x & 0xFF00FF00) | (rot16(x) & 0x00FF00FF);
}

/* 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, plus the fog coordinate as a float for the
 * specular colour. If both colours are requested then the arrays are
 * interleaved with diffuse first.
 */
static HRESULT GL_build_color_arrays(GL_D3D_priv* priv, DWORD fvf,
				     DWORD vstart, DWORD vcount,
				     LPGLDRAWPRIMITIVESTRIDEDDATA psd,
				     const WORD* indices, DWORD nindices,
				     DWORD ibase)
{
    LPDWORD out;
    DWORD which;
    DWORD size;

    which = fvf & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR);

    /* do the calculation assuming DIFFUSE and SPECULAR are set. */
    size = vcount * 3 * sizeof(D3DCOLOR);
    if (size > priv->color_array_size)
    {
	LPVOID new_array;

	size = max(size, 2 * priv->color_array_size);

	new_array = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
				 PAGE_READWRITE);

	if (new_array == NULL) return E_OUTOFMEMORY;

	VirtualFree(priv->color_array, 0, MEM_RELEASE);

	priv->color_array = new_array;
	priv->color_array_size = size;
    }

    out = priv->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. */

	DWORD diffuse_stride = psd->color[0].dwStride;
	LPBYTE diffuse_in = (LPBYTE)psd->color[0].lpvData;
	DWORD specular_stride = psd->color[1].dwStride;
	LPBYTE specular_in = (LPBYTE)psd->color[1].lpvData;

	if (indices != NULL && nindices < vcount)
	{
	    diffuse_in += ibase * diffuse_stride;
	    specular_in += ibase * specular_stride;
	    out -= (vstart - ibase) * 3;
	    while (nindices--)
	    {
		DWORD index = *indices++;

		out[3*index+0]
		    = GL_reorder_color(*(LPDWORD)(diffuse_in
						  + index * diffuse_stride));
		out[3*index+1]
		    = GL_reorder_color(*(LPDWORD)(specular_in
						  + index * specular_stride));
#ifdef COMPUTE_FOG_COORDINATE
		*((float*)(&out[3*index+2]))
		    = RGBA_GETALPHA(*(LPDWORD)(specular_in
					       + index * specular_stride)) / 255.0;
#endif
	    }
	}
	else
	{
	    diffuse_in += vstart * diffuse_stride;
	    specular_in += vstart * specular_stride;
	    while (vcount--)
	    {
		out[0] = GL_reorder_color(*(LPDWORD)diffuse_in);
		out[1] = GL_reorder_color(*(LPDWORD)specular_in);
#ifdef COMPUTE_FOG_COORDINATE
		*((float*)(&out[2])) = RGBA_GETALPHA(*(LPDWORD)specular_in) / 255.0;
#endif

		diffuse_in += diffuse_stride;
		specular_in += specular_stride;
		out += 3;
	    }
	}
    }
    else if (which == D3DFVF_DIFFUSE) {
	DWORD stride;
	LPBYTE in;

	stride = psd->color[0].dwStride;
	in = (LPBYTE)psd->color[0].lpvData;

	if (indices != NULL && nindices < vcount)
	{
	    in += ibase * stride;
	    out -= vstart - ibase;
	    while (nindices--)
	    {
		DWORD index = *indices++;

		out[index] = GL_reorder_color(*(LPDWORD)(in + index * stride));
	    }
	}
	else
	{
	    in += vstart * stride;
	    while (vcount--)
	    {
		*out++ = GL_reorder_color(*(LPDWORD)in);
		in += stride;
	    }
	}
    }
    else if (which == D3DFVF_SPECULAR) {
	DWORD stride;
	LPBYTE in;

	stride = psd->color[1].dwStride;
	in = (LPBYTE)psd->color[1].lpvData;

	if (indices != NULL && nindices < vcount)
	{
	    in += ibase * stride;
	    out -= (vstart - ibase) * 2;
	    while (nindices--)
	    {
		DWORD index = *indices++;

		out[2*index] = GL_reorder_color(*(LPDWORD)(in + index * stride));
#ifdef COMPUTE_FOG_COORDINATE
		*((float*)(&out[2*index+1])) = RGBA_GETALPHA(*(LPDWORD)(in + index * stride)) / 255.0;
#endif
	    }
	}
	else
	{
	    in += vstart * stride;
	    while (vcount--)
	    {
		out[0] = GL_reorder_color(*(LPDWORD)in);
#ifdef COMPUTE_FOG_COORDINATE
		*((float*)(&out[1])) = RGBA_GETALPHA(*(LPDWORD)in) / 255.0;
#endif
		in += stride;
		out += 2;
	    }
	}
    }

    return DD_OK;
}

static void GL_fixup_color_arrays(GL_D3D_priv* priv, DWORD fvf,
				  DWORD vstart, DWORD vcount,
				  LPGLDRAWPRIMITIVESTRIDEDDATA data,
				  const WORD* indices, DWORD nindices,
				  DWORD ibase)
{
    if ((fvf & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
	&& !(fvf & D3DFVF_GLCOLORORDER))
    {
	GL_build_color_arrays(priv, fvf, vstart, vcount, data, indices, nindices, ibase);

	switch (fvf & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
	{
	case D3DFVF_DIFFUSE|D3DFVF_SPECULAR:
	    data->color[0].lpvData = (LPBYTE)priv->color_array - vstart * 12;
	    data->color[0].dwStride = 12;
	    data->color[1].lpvData = (LPBYTE)priv->color_array+sizeof(D3DCOLOR) - vstart * 12;
	    data->color[1].dwStride = 12;
	    data->fog_coordinate.lpvData = (LPBYTE)priv->color_array+2*sizeof(D3DCOLOR) - vstart * 12;
	    data->fog_coordinate.dwStride = 12;
	    break;

	case D3DFVF_DIFFUSE:
	    data->color[0].lpvData = (LPBYTE)priv->color_array - vstart * 4;
	    data->color[0].dwStride = 4;
	    break;

	case D3DFVF_SPECULAR:
	    data->color[1].lpvData = (LPBYTE)priv->color_array - vstart * 8;
	    data->color[1].dwStride = 8;
	    data->fog_coordinate.lpvData = (LPBYTE)priv->color_array+sizeof(D3DCOLOR) - vstart * 8;
	    data->fog_coordinate.dwStride = 8;
	    break;
	}
    }
}

/* Set up for unlit rendering in which the materials not listed in
 * DIRTY will be using the global (IDirect3DDevice7::SetMaterial)
 * values.
 *
 * All dirty materials are cleaned, except those listed in DIRTY.
 * Then DIRTY is added to the set of dirty materials.
 */
static void GL_reset_materials(GL_D3D_priv* priv, DWORD dirty)
{
    DWORD flags = priv->material_flags & ~dirty;

    if (flags & GLMF_DIFFUSE_DIRTY)
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,
		     (float*)&priv->state.material.u.diffuse);

    if (flags & GLMF_AMBIENT_DIRTY)
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,
		     (float*)&priv->state.material.u1.ambient);

    if (priv->state.renderstate[D3DRENDERSTATE_SPECULARENABLE])
    {
	if (flags & GLMF_SPECULAR_DIRTY)
	    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
			 (float*)&priv->state.material.u2.specular);

	if (flags & GLMF_SHININESS_DIRTY)
	    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS,
			priv->state.material.u4.power);

	flags &= ~(GLMF_SPECULAR_DIRTY|GLMF_SHININESS_DIRTY);
    }
    else
    {
	if (flags & GLMF_SPECULAR_DIRTY)
	    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, zero4f);

	flags &= ~GLMF_SPECULAR_DIRTY;
    }

    if (flags & GLMF_EMISSIVE_DIRTY)
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,
		     (float*)&priv->state.material.u3.emissive);

    flags &= ~(GLMF_DIFFUSE_DIRTY|GLMF_AMBIENT_DIRTY|GLMF_EMISSIVE_DIRTY);

    priv->material_flags = flags | dirty;
}

/* Set up the mapping from vertex colour to material.
 *
 * This is used to draw unlit vertices when D3DRSS_COLORVERTEX is on.
 * It walks through the D3DRSS_*MATERIALSOURCE renderstates to decide
 * which will be mapped to the current color using glColorMaterial.
 *
 * NOTE: When this returns, GL_COLOR_MATERIAL may be enabled, so turn it off
 * once you are done rendering.
 *
 * Each material is either
 * - loaded from the current colour (i.e. by glColor(Pointer))
 * - marked with the GLMF_DO_material flag
 * - or clean.
 *
 * Materials that will be altered by the current colour or are marked
 * with a GLMF_DO_material flag are also marked dirty.
 */
static DWORD GL_fixup_vertex_materials(GL_D3D_priv* priv,
				       DWORD fvf,
				       LPGLDRAWPRIMITIVESTRIDEDDATA data)
{
    /* These will be set to the effective colour source for each material.
     * ("Effective" means after removing sources not present in the FVF
     * and specular if disabled.) */
    DWORD diffuse, specular, ambient, emissive;
    /* For the Diffuse&Ambient combo hack. (Sacrifice) */
    DWORD ambient2;

    /* Keep track of which colour source will be loaded into the array.
     * 0 means that it hasn't been assigned yet, otherwise it is
     * D3DMCS_COLOR1 or D3DMCS_COLOR2. */
    DWORD color_array = 0;
    GLenum color_material = 0;

    /* Keep flags for which materials will be changed while drawing. We won't
     * bother to clean these materials, and we'll mark them dirty right
     * here, knowing that the'll be changed shortly. */
    DWORD inuse = 0;

    DWORD material_flags = priv->material_flags;

    /* Pick out D3DFVF_DIFFUSE and D3DFVF_SPECULAR, mapping them to
     * D3DMCS_COLOR1 and D3DMCS_COLOR2 respectively. */
    DWORD vertex_colors = (fvf >> 6) & 3;

    priv->state.dirty_material = FALSE;

    /* Don't do anything the vertices are prelit, or vertex materials are
     * not in use at all. */
    if (ISTLFVF(fvf)
        || (!(fvf & D3DFVF_NORMAL) && priv->d3d_version < 8)
	|| !priv->state.renderstate[D3DRENDERSTATE_COLORVERTEX]
	|| !priv->state.renderstate[D3DRENDERSTATE_LIGHTING])
    {
	/* We don't have to disable GL_COLOR_MATERIAL when the vertices are
	 * prelit. Is it better to disable it or not? */
	if (material_flags & GLMF_COLOR_MATERIAL)
	{
	    glDisable(GL_COLOR_MATERIAL);
	}

	material_flags &= ~(GLMF_COLOR_MATERIAL|GLMF_FOG_COORDINATE);

	if (!ISTLFVF(fvf)
	    && ((fvf & D3DFVF_NORMAL) || priv->d3d_version >= 8)
	    && priv->state.renderstate[D3DRENDERSTATE_LIGHTING])
	{
	    GL_reset_materials(priv, 0);

	    /* Lighting is on, but color vertex is off, so we won't be
	     * needing these. */
	    fvf &= ~(D3DFVF_DIFFUSE|D3DFVF_SPECULAR);

	} else if (fvf & D3DFVF_SPECULAR) {
#ifdef COMPUTE_FOG_COORDINATE
	    /* Lighting is off, and we have specular, so we
	     * might have a fog coordinate */
	    /* If pixel fog is enabled, it takes precedence, I think
	     * (except for pretransformed vertices, as used by EverQuest, it seems) */
	    if (ISTLFVF(fvf)
		|| !priv->state.renderstate[D3DRENDERSTATE_FOGTABLEMODE]) {
		/* Pixel fog is disabled... what about vertex fog? */
		if (ISTLFVF(fvf)
		    || !priv->state.renderstate[D3DRENDERSTATE_FOGVERTEXMODE]) {
		    /* With vertex fog disabled, use the fog coordinate. */
		    material_flags |= GLMF_FOG_COORDINATE;
		}
	    }
#endif

	    if( !priv->state.renderstate[D3DRENDERSTATE_SPECULARENABLE] ) {
		/* specular is turned off, but fvf contains specular so remove it */
		fvf &= ~D3DFVF_SPECULAR;
	    }
	}

	if (((priv->material_flags ^ material_flags) & GLMF_FOG_COORDINATE) &&
	    HAVE_GL_EXT(priv, EXT_fog_coord)) {
	    glFogi(GL_FOG_COORDINATE_SOURCE_EXT,
		   (material_flags & GLMF_FOG_COORDINATE) ? GL_FOG_COORDINATE_EXT
							  : GL_FRAGMENT_DEPTH_EXT);
	}

	priv->material_flags = material_flags;

	return fvf;
    }

    if ((material_flags & GLMF_FOG_COORDINATE) && HAVE_GL_EXT(priv, EXT_fog_coord)) {
	glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FRAGMENT_DEPTH_EXT);
    }

    material_flags &= ~(GLMF_DO_DIFFUSE|GLMF_DO_SPECULAR|GLMF_DO_AMBIENT
			|GLMF_DO_EMISSIVE|GLMF_FOG_COORDINATE);

    diffuse = priv->state.renderstate[D3DRENDERSTATE_DIFFUSEMATERIALSOURCE]
	& vertex_colors;
    specular = priv->state.renderstate[D3DRENDERSTATE_SPECULARMATERIALSOURCE]
	& vertex_colors;
    ambient = priv->state.renderstate[D3DRENDERSTATE_AMBIENTMATERIALSOURCE]
	& vertex_colors;
    emissive = priv->state.renderstate[D3DRENDERSTATE_EMISSIVEMATERIALSOURCE]
	& vertex_colors;

    /* ignore specular if it is disabled. */
    if (!priv->state.renderstate[D3DRENDERSTATE_SPECULARENABLE])
	specular = D3DMCS_MATERIAL;

    /* for the purposes of deciding whether or not to fallback,
     * ignore ambient if it is the same as diffuse */
    ambient2 = ambient;
    if (ambient2 == diffuse)
	ambient2 = D3DMCS_MATERIAL;

    /* If two materials are using vertex colours (except for the diffuse
     * & ambient combo), then we will need to use immediate-mode calls. */
    if (((diffuse != D3DMCS_MATERIAL) + (ambient2 != D3DMCS_MATERIAL)
	 + (specular != D3DMCS_MATERIAL)
	 + (emissive != D3DMCS_MATERIAL)) > 1) {
	priv->glFlags &= ~GLF_USE_VERTEX_ARRAYS;
    }

    /* Choose a material that will be mapped to the current colour.
     * Diffuse goes first so that we can prefer the diffuse & ambient combo.
     * Also mark which materials will have to be loaded by glMaterial. */
    if (diffuse != D3DMCS_MATERIAL)
    {
	if (diffuse == ambient)
	{
	    assert(ambient2 == D3DMCS_MATERIAL);
	    color_material = GL_AMBIENT_AND_DIFFUSE;
	    inuse |= GLMF_AMBIENT_DIRTY;
	    TRACE("choosing GL_DIFFUSE_AND_AMBIENT\n");
	}
	else
	{
	    color_material = GL_DIFFUSE;
	    TRACE("choosing GL_DIFFUSE\n");
	}

	color_array = diffuse;
	inuse |= GLMF_DIFFUSE_DIRTY;
    }

    if (ambient2 != D3DMCS_MATERIAL)
    {
	assert(ambient2 == ambient);

	if (color_array == 0)
	{
	    color_material = GL_AMBIENT;
	    color_array = ambient;
	    TRACE("choosing GL_AMBIENT\n");
	}
	else
	{
	    material_flags |= GLMF_DO_AMBIENT;
	}

	inuse |= GLMF_AMBIENT_DIRTY;
    }

    if (specular != D3DMCS_MATERIAL)
    {
	if (color_array == 0)
	{
	    color_material = GL_SPECULAR;
	    color_array = specular;
	    TRACE("choosing GL_SPECULAR\n");
	}
	else
	{
	    material_flags |= GLMF_DO_SPECULAR;
	}

	inuse |= GLMF_SPECULAR_DIRTY;
    }

    if (emissive != D3DMCS_MATERIAL)
    {
	if (color_array == 0)
	{
	    color_material = GL_EMISSION;
	    color_array = emissive;
	    TRACE("choosing GL_EMISSIVE\n");
	}
	else
	{
	    material_flags |= GLMF_DO_EMISSIVE;
	}

	inuse |= GLMF_EMISSIVE_DIRTY;
    }

    priv->material_flags = material_flags;

    if (color_array != 0)
    {
	if (color_material != priv->current_color_material)
	{
	    glColorMaterial(GL_FRONT_AND_BACK, color_material);
	    priv->current_color_material = color_material;
	}

	if (!(material_flags & GLMF_COLOR_MATERIAL))
	{
	    glEnable(GL_COLOR_MATERIAL);
	    priv->material_flags |= GLMF_COLOR_MATERIAL;
	}

	/* Now put the in-use colour into slot 0, and tell the colour
	 * fixup and loader code to use that. */

	if (color_array == D3DMCS_COLOR2)
	    data->color[0] = data->color[1];

	GL_reset_materials(priv, inuse);

	return (fvf &~ D3DFVF_SPECULAR) | D3DFVF_DIFFUSE;
    }
    else
    {
	if (material_flags & GLMF_COLOR_MATERIAL)
	{
	    glDisable(GL_COLOR_MATERIAL);
	    priv->material_flags &= ~GLMF_COLOR_MATERIAL;
	}

	GL_reset_materials(priv, inuse);

	/* We should detect when vertex colours are specified in the fvf,
	 * but not actually used (because only global materials are in use)
	 * and mask them off here. */
	return fvf;
    }
}

static void GL_load_arrays(GL_D3D_priv* priv, DWORD fvf, DWORD start,
			   LPGLDRAWPRIMITIVESTRIDEDDATA stride)
{
    DWORD position_size, num_weights;
    DWORD inuse, tstages;
    DWORD i;

    if (ISTLFVF(fvf))
	position_size = 4;
    else
	position_size = 3;

    num_weights = D3D_GL_BlendWeights(fvf);
    if (num_weights && (fvf & D3DFVF_LASTBETA_UBYTE4)) num_weights--;

#define STRIDE_DATA_STRIDE(x) \
  stride->x.dwStride
#define STRIDE_DATA_PTR(x) \
  ((LPBYTE)stride->x.lpvData + start * stride->x.dwStride)
#define STRIDE_DATA(x) \
  STRIDE_DATA_STRIDE(x), STRIDE_DATA_PTR(x)

    if (!priv->pVertexArray)
	glEnableClientState(GL_VERTEX_ARRAY);
    if (priv->pVertexArray != STRIDE_DATA_PTR(position) ||
	priv->sVertexArray != STRIDE_DATA_STRIDE(position))
    {
	glVertexPointer(position_size, GL_FLOAT, STRIDE_DATA(position));
	priv->pVertexArray = STRIDE_DATA_PTR(position);
	priv->sVertexArray = STRIDE_DATA_STRIDE(position);
    }

    if (fvf & D3DFVF_NORMAL)
    {
	if (!priv->pNormalArray)
	    glEnableClientState(GL_NORMAL_ARRAY);
	if (priv->pNormalArray != STRIDE_DATA_PTR(normal) ||
	    priv->sNormalArray != STRIDE_DATA_STRIDE(normal))
	{
	    glNormalPointer(GL_FLOAT, STRIDE_DATA(normal));
	    priv->pNormalArray = STRIDE_DATA_PTR(normal);
	    priv->sNormalArray = STRIDE_DATA_STRIDE(normal);
	}
    }
    else
    {
	if (priv->pNormalArray)
	{
	    glDisableClientState(GL_NORMAL_ARRAY);
	    priv->pNormalArray = NULL;
	    priv->sNormalArray = 0;

	    /* YES it's necessary - D3D8 allows (ambient) lighting on vertices
	     * without normals, don't leave it undefined */
	    glNormal3f(0, 0, 0);
	}
    }

    if (fvf & D3DFVF_DIFFUSE)
    {
	if (!priv->pColorArray)
	    glEnableClientState(GL_COLOR_ARRAY);
	if (priv->pColorArray != STRIDE_DATA_PTR(color[0]) ||
	    priv->sColorArray != STRIDE_DATA_STRIDE(color[0]))
	{
	    glColorPointer(4, GL_UNSIGNED_BYTE, STRIDE_DATA(color[0]));
	    priv->pColorArray = STRIDE_DATA_PTR(color[0]);
	    priv->sColorArray = STRIDE_DATA_STRIDE(color[0]);
	}
    }
    else
    {
	if (priv->pColorArray)
	{
	    glDisableClientState(GL_COLOR_ARRAY);
	    priv->pColorArray = NULL;
	    priv->sColorArray = 0;

	    /* YES it's necessary - D3D (all versions) defaults to white
	     * when no colors are specified, don't leave it undefined */
	    glColor4fv(one4f);
	}
    }

    if (HAVE_GL_EXT(priv, EXT_secondary_color))
    {
	if (fvf & D3DFVF_SPECULAR)
	{
	    if (!priv->pSecondaryColorArray) 
		glEnableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
	    if (priv->pSecondaryColorArray != STRIDE_DATA_PTR(color[1]) ||
	        priv->sSecondaryColorArray != STRIDE_DATA_STRIDE(color[1]))
	    {
		GLD(priv)->glSecondaryColorPointerEXT(3, GL_UNSIGNED_BYTE,
						      STRIDE_DATA(color[1]));
		priv->pSecondaryColorArray = STRIDE_DATA_PTR(color[1]);
		priv->sSecondaryColorArray = STRIDE_DATA_STRIDE(color[1]);
	    }
	}
	else
	{
	    if (priv->pSecondaryColorArray)
	    {
		glDisableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
		priv->pSecondaryColorArray = NULL;
		priv->sSecondaryColorArray = 0;

		/* YES it's necessary - we don't want to leave
		 * the secondary color undefined */
		GLD(priv)->glSecondaryColor3ubEXT(0, 0, 0);
	    }
	}
    }

    if (HAVE_GL_EXT(priv, EXT_fog_coord))
    {
	if (priv->material_flags & GLMF_FOG_COORDINATE)
	{
	    if (!priv->pFogCoordArray)
		glEnableClientState(GL_FOG_COORDINATE_ARRAY_EXT);
	    if (priv->pFogCoordArray != STRIDE_DATA_PTR(fog_coordinate) ||
	        priv->sFogCoordArray != STRIDE_DATA_STRIDE(fog_coordinate))
	    {
		GLD(priv)->glFogCoordPointerEXT(GL_FLOAT,
						STRIDE_DATA(fog_coordinate));
		priv->pFogCoordArray = STRIDE_DATA_PTR(fog_coordinate);
		priv->sFogCoordArray = STRIDE_DATA_STRIDE(fog_coordinate);
	    }
	}
	else
	{
	    if (priv->pFogCoordArray)
	    {
		glDisableClientState(GL_FOG_COORDINATE_ARRAY_EXT);
		priv->pFogCoordArray = NULL;
		priv->sFogCoordArray = 0;

		/* this may not be necessary */
		GLD(priv)->glFogCoordfEXT(0);
	    }
	}
    }

    tstages = priv->lpCaps->wMaxTextureBlendStages;
    inuse = D3DFVF_TEXCOUNT(fvf);

    for (i = 0; i < tstages; i++)
    {
	DWORD coord_idx = priv->state.texstagestate[i][D3DTSS_TEXCOORDINDEX];

	GLD(priv)->glClientActiveTextureARB(GL_TEXTURE0_ARB + i);

	if ((coord_idx & D3DTSS_TCI_MASK) || (coord_idx >= inuse))
	{
	    /* Automatic texture coordinate generation is in use,
	     * or coord_idx is out of range */
	    if (priv->pTexCoordArray[i])
	    {
		GLD(priv)->glClientActiveTextureARB(GL_TEXTURE0_ARB + i);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		priv->pTexCoordArray[i] = NULL;
		priv->sTexCoordArray[i] = 0;

		/* this may be less necessary, not sure */
		GLD(priv)->glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 0);
	    }
	}
	else
	{
	    /* get the number of texcoords */
	    GLint num_coords = D3DFVF_TEXCOORDSIZE(fvf, coord_idx);  

	    if (!priv->pTexCoordArray[i])
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	    if (priv->pTexCoordArray[i] != STRIDE_DATA_PTR(textureCoords[coord_idx]) ||
	        priv->sTexCoordArray[i] != STRIDE_DATA_STRIDE(textureCoords[coord_idx]))
	    {
		glTexCoordPointer(num_coords, GL_FLOAT,
				  STRIDE_DATA(textureCoords[coord_idx]));
		priv->pTexCoordArray[i] = STRIDE_DATA_PTR(textureCoords[coord_idx]);
		priv->sTexCoordArray[i] = STRIDE_DATA_STRIDE(textureCoords[coord_idx]);
	    }
	}
    }

    if (HAVE_GL_EXT(priv, ARB_vertex_blend))
    {
	if (num_weights)
	{
	    glEnableClientState(GL_WEIGHT_ARRAY_ARB);
	    GLD(priv)->glWeightPointerARB(num_weights, GL_FLOAT,
					  STRIDE_DATA(blend_weights));
	}
	else
	{
	    glDisableClientState(GL_WEIGHT_ARRAY_ARB);
	}
	/* TODO: matrix palette indices */
    }
    else if (HAVE_GL_EXT(priv, EXT_vertex_weighting))
    {
	if (num_weights)
	{
	    glEnableClientState(GL_VERTEX_WEIGHT_ARRAY_EXT);
	    GLD(priv)->glVertexWeightPointerEXT(num_weights, GL_FLOAT,
						STRIDE_DATA(blend_weights));
	}
	else
	{
	    glDisableClientState(GL_VERTEX_WEIGHT_ARRAY_EXT);
	}
    }
    /* TODO: fog */

#undef STRIDE_DATA
}

static void GL_clear_texture_transforms(GL_D3D_priv* priv)
{
    DWORD tstages = priv->lpCaps->wMaxTextureBlendStages;
    DWORD i;

    if (priv->texture_flags & GLTF_DEFAULT_TEXCOORD_TRANSFORM) return;

    glMatrixMode(GL_TEXTURE);

    for (i = 0; i < tstages; i++)
    {
	GLD(priv)->glActiveTextureARB(GL_TEXTURE0_ARB + i);

	glLoadIdentity();

	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
    }
}

static void GL_setup_texture_transforms(GL_D3D_priv* priv, DWORD vt)
{
    DWORD tstages = priv->lpCaps->wMaxTextureBlendStages;
    DWORD def, i;

    def = GLTF_DEFAULT_TEXCOORD_TRANSFORM;
    if (priv->texture_flags & def) return;

    glMatrixMode(GL_TEXTURE);

    for (i=0; i < tstages; i++)
    {
	DWORD coord_idx = priv->state.texstagestate[i][D3DTSS_TEXCOORDINDEX];
	BOOL want_texgen = FALSE;
	GLenum texgen_mode = 0;
	DWORD in_coords;

	GLD(priv)->glActiveTextureARB(GL_TEXTURE0_ARB + i);

	if (coord_idx & D3DTSS_TCI_MASK)
	{
	    switch (coord_idx >> 16)
	    {
	    case D3DTSS_TCI_CAMERASPACEPOSITION >> 16:
		/* We assume that the eye plane has been set up to map
		 * x,y,z to s,t,r. See D3D_GL_init. */
		texgen_mode = GL_EYE_LINEAR;
		want_texgen = TRUE;
		break;

	    case D3DTSS_TCI_CAMERASPACENORMAL >> 16:
		if (HAVE_GL_EXT(priv, ARB_texture_cube_map)
		    || HAVE_GL_EXT(priv, NV_texgen_reflection))
		{
		    texgen_mode = GL_NORMAL_MAP_ARB;
		    want_texgen = TRUE;
		}
		break;

	    case D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR >> 16:
		if (HAVE_GL_EXT(priv, ARB_texture_cube_map)
		    || HAVE_GL_EXT(priv, NV_texgen_reflection))
		{
		    texgen_mode = GL_REFLECTION_MAP_ARB;
		    want_texgen = TRUE;
		}
		break;
	    }
	}

	if (want_texgen)
	{
	    def = 0;
	    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, texgen_mode);
	    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, texgen_mode);
	    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, texgen_mode);
	    glEnable(GL_TEXTURE_GEN_S);
	    glEnable(GL_TEXTURE_GEN_T);
	    glEnable(GL_TEXTURE_GEN_R);
	    in_coords = 3;
	}
	else
	{
	    static const DWORD ics[4] = {2,3,4,1};
	    glDisable(GL_TEXTURE_GEN_S);
	    glDisable(GL_TEXTURE_GEN_T);
	    glDisable(GL_TEXTURE_GEN_R);
	    if (i >= D3DFVF_TEXCOUNT(vt)) in_coords = 0;
	    else in_coords = ics[(vt >> (16 + i*2)) & 3];
	}

	if (priv->state.texstagestate[i][D3DTSS_TEXTURETRANSFORMFLAGS]
	    == D3DTTFF_DISABLE)
	{
	    glLoadIdentity();
	}
	else
	{
	    /* We interpret D3DTTFF_COUNTX and D3DTTFF_PROJECTED as
	     * follows:
	     *
	     * All extra dimensions (beyond the count setting) are set
	     * to 0, except for q which is set to 1.
	     * If D3DTTFF_PROJECTED is set then the last dimension is
	     * sent to q, and is set to 0.
	     *
	     * So for example with D3DTTFF_COUNT3|D3DTTFF_PROJECTED,
	     * have s' = s, t'=t, r'=0, q'=r.
	     *
	     * Note that D3DTTFF_COUNT4 and
	     * D3DTTFF_COUNT4|D3DTTFF_PROJECTED are the same because
	     * GL always divides by the 4th coordinate.
	     */
	    D3DTEXTURETRANSFORMFLAGS flags
		= priv->state.texstagestate[i][D3DTSS_TEXTURETRANSFORMFLAGS];

	    unsigned out_coords = flags & D3DTTFF_COUNT_MASK;

	    float tmp[4][4];
	    unsigned j, k;

	    def = 0;

	    if (out_coords > 3) {
		flags |= D3DTTFF_PROJECTED;
		out_coords = 3;
	    }
	    else if (flags & D3DTTFF_PROJECTED)
		out_coords--;

	    for (j=0; j<in_coords; j++) {
		for (k=0; k<out_coords; k++)
		    tmp[j][k] = priv->state.txfrm[i].u.m[j][k];
		for (; k<3; k++)
		    tmp[j][k] = 0.0f;
		tmp[j][3] = (flags & D3DTTFF_PROJECTED) ? priv->state.txfrm[i].u.m[j][out_coords] : 0.0f;
	    }
	    for (; j<3; j++) {
		for (k=0; k<4; k++)
		    tmp[j][k] = 0.0f;
	    }
	    for (k=0; k<out_coords; k++)
		tmp[3][k] = priv->state.txfrm[i].u.m[in_coords][k];
	    for (; k<3; k++)
		tmp[3][k] = 0.0f;
	    tmp[3][3] = (flags & D3DTTFF_PROJECTED) ? priv->state.txfrm[i].u.m[in_coords][out_coords] : 1.0f;

	    glLoadMatrixf((const float*)tmp);
	}
    }

    priv->texture_flags |= def;
}

#ifndef HAVE_OSMESA

static const GLfloat clipspace_fix[] =
{
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 2, 0,
    0, 0, -1, 1
};

#else

/* OSMESA y-swap */
static const GLfloat clipspace_fix[] =
{
    1, 0, 0, 0,
    0, -1, 0, 0, /* FLIP */
    0, 0, 2, 0,
    0, 0, -1, 1
};

#endif

void GL_setup_transforms(GL_D3D_priv* priv, DWORD vt)
{
    if (priv->glFlags & (GLF_NEW_TNL_MODE | GLF_NEW_TRANSFORM)) {
	if (ISTLFVF(vt)) {
	    GLdouble left, top, height, width, minZ, maxZ;

	    TRACE("Transformed Vertices\n");

	    /* Do not put any transformation matrixes */
	    glMatrixMode(GL_MODELVIEW);
	    glLoadIdentity();
	    /* the Red Book (under "OpenGL Correctness Tips") recommends offseting coordinates
	     * by 0.375 to be sure that integer pixel coordinates can be used in all cases...
	     * I'm not sure what the best value to use is for us, though (0.5? 0.25?),
	     * but in order to get nearest-filtered textures mapped correctly, it must be
	     * *something*, due to the half-pixel issue discussed below...
	     * so I think I'll use the book's recommendation for now
	     * (this fixes ugly EverQuest fonts seen on some systems) */
	    glTranslatef(0.375, 0.375, 0);

	    glMatrixMode(GL_PROJECTION);
	    glLoadIdentity();
	    if (HAVE_GL_EXT(priv, ARB_vertex_blend))
		glDisable(GL_VERTEX_BLEND_ARB);
	    else if (HAVE_GL_EXT(priv, EXT_vertex_weighting))
		glDisable(GL_VERTEX_WEIGHTING_EXT);

	    left   = priv->state.viewport.dwX;
	    top    = priv->state.viewport.dwY;
	    height = priv->state.viewport.dwHeight;
	    width  = priv->state.viewport.dwWidth;
	    if( use_clipspace_fix ) {
	      minZ = priv->state.viewport.dvMinZ;
	      maxZ = priv->state.viewport.dvMaxZ;
	    } else {
	      minZ = 2*priv->state.viewport.dvMinZ - priv->state.viewport.dvMaxZ; /* min - (max-min) = 2*min - max */
	      maxZ = priv->state.viewport.dvMaxZ;
	    }

	    /* TODO: Use 0 and 1 for Znear, Zfar. */
	    /* TODO: glDepthRange(0, 1); */
	    if (priv->glFlags & GLF_RENDER_INVERTED)
		glOrtho(left, left + width, top, top + height, -minZ, -maxZ);
	    else
		glOrtho(left, left + width, top + height, top, -minZ, -maxZ);

	    GL_clear_texture_transforms(priv);
	}
	else {
	    TRACE("Untransformed Vertices\n");

	    /* Need to put the correct transformation again */
	    switch (priv->state.renderstate[D3DRENDERSTATE_VERTEXBLEND]) {
	    default:
	    case D3DVBLEND_3WEIGHTS:
		glMatrixMode(GL_MODELVIEW3_ARB);
		glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
		glMultMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_WORLD3]);
		/* fall thru */
	    case D3DVBLEND_2WEIGHTS:
		glMatrixMode(GL_MODELVIEW2_ARB);
		glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
		glMultMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_WORLD2]);
		/* fall thru */
	    case D3DVBLEND_1WEIGHT:
		glMatrixMode(GL_MODELVIEW1_ARB);
		glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
		glMultMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_WORLD1]);
		/* fall thru */
	    case D3DVBLEND_DISABLE:
		glMatrixMode(GL_MODELVIEW0_ARB); /* aka standard GL_MODELVIEW */
		glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
		glMultMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_WORLD]);
	    }

	    glMatrixMode(GL_PROJECTION);
	    if( use_clipspace_fix ) {
	      /* in opengl the z clipping is -W < z <= W   but in d3d it is 0 < z <= w
	       * this matrix makes openlg clip like d3d */
	      glLoadMatrixf(clipspace_fix);
	    } else {
	      /* however, with vertex programs, we cannot easily apply such a fix, and
	       * apps the mix fixed-function rendering with programmable, end up with
	       * conflicting depth info.  This lets us disable it on a per-app basis. */
	      glLoadIdentity(); 
	    }
	    /* the OpenGL spec states:
	     *  "A fragment is located by its lower-left corner, which lies on integer grid coordinates"
	     * the Direct3D rasterization rules state:
	     *  "Pixel centers are at integer coordinates"
	     * so it seems we need to translate the pre-rasterization coordinates by
	     * half a pixel in order to get nearest-filtered texture maps (and the polygons
	     * that they are mapped onto) mapped correctly
	     * (this fixes ugly WarCraft III fonts seen on some systems)
	     *
	     * Addendum:
	     *  "Direct3D uses a top-left filling convention"
	     * so we must also bias the coordinates slightly to the top left
	     * (this fixes ugly Battlefield 1942 fonts seen on some systems)
	     */
	    if (priv->glFlags & GLF_RENDER_INVERTED) glScalef(1.0, -1.0, 1.0);
	    glTranslatef(2*0.45/priv->state.viewport.dwWidth, -2*0.45/priv->state.viewport.dwHeight, 0);
	    if (priv->glFlags & GLF_USE_CLIPMATRIX) {
		/* DirectX 5/6 viewport clip transform */
		glMultMatrixf((const float *)&priv->state.xfrm[0]);
	    }
	    glMultMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_PROJECTION]);

	    GL_setup_texture_transforms(priv, vt);

	    /* TODO: Reload glDepthRange. */

	    /* Configure vertex blending */
	    if (HAVE_GL_EXT(priv, ARB_vertex_blend)) {
		if (priv->state.renderstate[D3DRENDERSTATE_VERTEXBLEND]) {
		    glEnable(GL_VERTEX_BLEND_ARB);
		    GLD(priv)->glVertexBlendARB(priv->state.renderstate[D3DRENDERSTATE_VERTEXBLEND] + 1);
		}
		else
		    glDisable(GL_VERTEX_BLEND_ARB);
	    } else if (HAVE_GL_EXT(priv, EXT_vertex_weighting)) {
		if (priv->state.renderstate[D3DRENDERSTATE_VERTEXBLEND])
		    glEnable(GL_VERTEX_WEIGHTING_EXT);
		else
		    glDisable(GL_VERTEX_WEIGHTING_EXT);
	    }
	}
	priv->glFlags &= ~(GLF_NEW_TRANSFORM | GLF_NEW_TEXCOORD_TRANSFORM);
    }
    else if (priv->glFlags & GLF_NEW_TEXCOORD_TRANSFORM)
    {
	GL_setup_texture_transforms(priv, vt);
	priv->glFlags &= ~GLF_NEW_TEXCOORD_TRANSFORM;
    }
    D3D_GL_update_clip_planes(priv, priv->glFlags & GLF_NEW_VIEW_XFRM, TRUE);
    priv->glFlags &= ~GLF_NEW_VIEW_XFRM;
}

void GL_setup_lighting(GL_D3D_priv* priv, DWORD vt)
{
    /* DX8: Only the render state should be used to distinguish prelit,
     * but apparently only for untransformed vertices */
    if (priv->state.renderstate[D3DRENDERSTATE_LIGHTING] &&
	!ISTLFVF(vt) &&
	((vt & D3DFVF_NORMAL) || (priv->d3d_version >= 8))) {
	TRACE("Unlit Vertices\n");
	if (!priv->isLightingEnabled)
	{
	    glEnable(GL_LIGHTING);
	    priv->isLightingEnabled = TRUE;
	}
	if (priv->glFlags & GLF_NEW_LIGHT_DATA)
	    GL_LoadLights(priv, priv->glFlags & GLF_NEW_VIEW_LIGHT);
	else if (priv->glFlags & GLF_NEW_VIEW_LIGHT)
	    GL_LoadLightPositions(priv, priv->glFlags & GLF_NEW_VIEW_LIGHT);
	priv->glFlags &= ~(GLF_NEW_VIEW_LIGHT | GLF_NEW_LIGHT_DATA);
    } else {
	TRACE("Lit Vertices\n");
	if (priv->isLightingEnabled)
	{
	    glDisable(GL_LIGHTING);
	    priv->isLightingEnabled = FALSE;
	}
    }
}

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

/* It is assumed that materials being set are marked dirty, and materials
 * not being set are clean. */
void GL_draw_unlit_vertices(GL_D3D_priv* priv, GLenum gltype, DWORD vt,
			    DWORD start, DWORD count, GL_IndexFunc index,
			    LPVOID index_data, DWORD ibase)
{
    DWORD stop = start + count;

    const D3DDP_PTRSTRIDE *diffuse = NULL;
    const D3DDP_PTRSTRIDE *specular = NULL;
    const D3DDP_PTRSTRIDE *ambient = NULL;
    const D3DDP_PTRSTRIDE *emissive = NULL;

    BOOL do_diffuse = priv->material_flags & GLMF_DO_DIFFUSE;
    BOOL do_specular = priv->material_flags & GLMF_DO_SPECULAR;
    BOOL do_ambient = priv->material_flags & GLMF_DO_AMBIENT;
    BOOL do_emissive = priv->material_flags & GLMF_DO_EMISSIVE;

    DWORD cnt;

    assert(vt & D3DFVF_NORMAL || priv->d3d_version >= 8);
    assert(vt & D3DFVF_POSITION_MASK && !ISTLFVF(vt));
    assert(glIsEnabled(GL_LIGHTING));
    assert(priv->state.renderstate[D3DRENDERSTATE_COLORVERTEX]);

    TRACE("%d: %ld %ld %ld %ld\n",
	  do_diffuse+do_specular+do_ambient+do_emissive,
	  priv->state.renderstate[D3DRENDERSTATE_DIFFUSEMATERIALSOURCE],
	  priv->state.renderstate[D3DRENDERSTATE_SPECULARMATERIALSOURCE],
	  priv->state.renderstate[D3DRENDERSTATE_AMBIENTMATERIALSOURCE],
	  priv->state.renderstate[D3DRENDERSTATE_EMISSIVEMATERIALSOURCE]);

    if (do_diffuse)
	diffuse = &priv->stride.color[priv->state.renderstate[D3DRENDERSTATE_DIFFUSEMATERIALSOURCE] - 1];

    if (do_specular)
	specular = &priv->stride.color[priv->state.renderstate[D3DRENDERSTATE_SPECULARMATERIALSOURCE] - 1];

    if (do_ambient)
	ambient = &priv->stride.color[priv->state.renderstate[D3DRENDERSTATE_AMBIENTMATERIALSOURCE] - 1];

    if (do_emissive)
	emissive = &priv->stride.color[priv->state.renderstate[D3DRENDERSTATE_EMISSIVEMATERIALSOURCE] - 1];

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

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

	    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_DIFFUSE, color);
	}

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

	    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+ibase);

	    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+ibase);

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

/* Determine if vt is directly supported by OpenGL given the current
 * renderstates and settings. */
static BOOL GL_IsDirectlyRenderable(GL_D3D_priv* priv, DWORD vt)
{
    /* RHW is directly supported if it has been converted to OpenGL format. */
    if ((vt & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW)
	return FALSE;

    if (vt & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
    {
	DWORD vertex_colors = (vt >> 6) & 3;
	DWORD diffuse, specular, ambient, emissive;

	/* If the vertex is prelit, then any specified vertex colours are
	 * in use. */
	if (ISTLFVF(vt)
	    || (!(vt & D3DFVF_NORMAL) && priv->d3d_version < 8)
	    || !priv->state.renderstate[D3DRENDERSTATE_COLORVERTEX]
	    || !priv->state.renderstate[D3DRENDERSTATE_LIGHTING])
	    return (vt & D3DFVF_GLCOLORORDER);

	diffuse = priv->state.renderstate[D3DRENDERSTATE_DIFFUSEMATERIALSOURCE]
	    & vertex_colors;
	specular = priv->state.renderstate[D3DRENDERSTATE_SPECULARMATERIALSOURCE]
	    & vertex_colors;
	ambient = priv->state.renderstate[D3DRENDERSTATE_AMBIENTMATERIALSOURCE]
	    & vertex_colors;
	emissive = priv->state.renderstate[D3DRENDERSTATE_EMISSIVEMATERIALSOURCE]
	    & vertex_colors;

	/* ignore specular if it is disabled. */
	if (!priv->state.renderstate[D3DRENDERSTATE_SPECULARENABLE])
	    specular = D3DMCS_MATERIAL;

	/* for the purposes of deciding whether or not to fallback,
	 * ignore ambient if it is the same as diffuse */
	if (ambient == diffuse)
	    ambient = D3DMCS_MATERIAL;

	/* If two materials are using vertex colours (except for the diffuse
	 * & ambient combo), then we will need to use immediate-mode calls. */
	if (((diffuse != D3DMCS_MATERIAL) + (ambient != D3DMCS_MATERIAL)
	     + (specular != D3DMCS_MATERIAL)
	     + (emissive != D3DMCS_MATERIAL)) > 1)
	    return FALSE;

	/* If any vertex colours are in use, then we need them to be in
	 * OpenGL order.
	 * NOTE: The bitwise | is a silly optimisation, not a typo. */
	if (diffuse | ambient | specular | emissive)
	    return (vt & D3DFVF_GLCOLORORDER);
    }

    return TRUE;
}

/* This is common to setting up from either a D3D stride, or from
 * a pointer and FVF. */
static void
GL_SetupStrideBlendWeights(DWORD fvf, LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    int num_weights = D3D_GL_BlendWeights(fvf);

    /* Blend weights immediately follow the position data.
     * There are a maximum of 4: XYZB5 does not actually specify 5 blend
     * weights since it must be used with D3DFVF_LASTBETA_UBYTE4 flag,
     * meaning that the last float is actually 4 matrix palette indices. */
    gl->blend_weights.lpvData = (LPFLOAT)gl->position.lpvData + 3;
    gl->blend_weights.dwStride = gl->position.dwStride;

    /* Setup the last blend weight as the matrix index pointer.
     * (In case that's what it actually is.) */
    gl->matrix_indices.lpvData
	= (LPFLOAT)gl->position.lpvData + 3 + num_weights - 1;
}

/* Apparently there is a super-secret fog value hidden in specular alpha. */
static void
GL_SetupStrideFogCoordinate(DWORD fvf, LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    if (!(fvf & D3DFVF_SPECULAR)) {
	/* nothing to do */
    }
    else if (fvf & D3DFVF_GLCOLORORDER) {
	gl->fog_coordinate.lpvData = ((LPD3DCOLOR)gl->color[1].lpvData) + 1;
	gl->fog_coordinate.dwStride = gl->color[1].dwStride;
    } else {
	/* FIXME: not endian-safe */
	gl->fog_coordinate.lpvData = ((LPBYTE)gl->color[1].lpvData) + 3;
	gl->fog_coordinate.dwStride = gl->color[1].dwStride;
    }
}

static void
GL_ConvertD3DStrideToGLStride(DWORD fvf,
			      const D3DDRAWPRIMITIVESTRIDEDDATA *d3d,
			      LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    int i;

    gl->position = d3d->position;
    gl->normal   = d3d->normal;
    gl->color[0] = d3d->diffuse;
    gl->color[1] = d3d->specular;

    for (i = 0; i < D3DDP_MAXTEXCOORD; i++)
	gl->textureCoords[i] = d3d->textureCoords[i];

    GL_SetupStrideBlendWeights(fvf, gl);
    GL_SetupStrideFogCoordinate(fvf, gl);
}

static void GL_VertexStream8PtrStride(GL_D3D_priv* priv, BYTE reg,
				      D3DDP_PTRSTRIDE* sd)
{
    VertexShader *vs = priv->current_vs;
    BYTE streamnum = vs->input_reg[reg].stream;
    DWORD ofs = vs->input_reg[reg].offset;
    if (streamnum || (priv->glFlags & GLF_VERTEX_BUFFER)) {
      /* normal case */
      sd->dwStride = priv->d3dp->stream[streamnum].stride;
      sd->lpvData = (LPVOID)priv->d3dp->stream[streamnum].data_t->gbl.fpVidMem + ofs;
    }
    else if (priv->glFlags & GLF_STRIDED_DATA) {
      /* stream zero user pointer (Draw[Indexed]PrimitiveUP) */
      sd->dwStride = priv->sd.position.dwStride;
      sd->lpvData = priv->sd.position.lpvData + ofs;
    }
    else {
      FIXME("vertex declaration stream zero badness\n");
      sd->dwStride = priv->d3dp->stream[0].stride;
      sd->lpvData = (LPVOID)priv->d3dp->stream[0].data_t->gbl.fpVidMem + ofs;
    }
}

static DWORD
GL_VertexDeclaration8ToGLStride(GL_D3D_priv* priv, LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    VertexShader *vs = priv->current_vs;
    DWORD fvf = 0;
    DWORD texcount = 0;
    int i;

    TRACE("converting vertex shader declaration to gl stride\n");
    /* position */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_POSITION].type) ) {
      GL_VertexStream8PtrStride(priv, D3DVSDE_POSITION, &gl->position);
      switch(GET_INPUT_REG_TYPE(vs->input_reg[D3DVSDE_POSITION].type)) {
	case D3DVSDT_FLOAT3: fvf |= D3DFVF_XYZ; break;
	default: TRACE("unhandled data format for D3DVSDE_POSITION\n");
      }
    }
    /* blend weights */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_BLENDWEIGHT].type) ) {
      /* if the position has not been set, these are XYZRHW, otherwise blend weights 
       * (at least that's the best I can decipher from the MS docs) */
      if( !fvf ) {
        GL_VertexStream8PtrStride(priv, D3DVSDE_BLENDWEIGHT, &gl->position);
	fvf = D3DFVF_XYZRHW;
      } else {
        GL_VertexStream8PtrStride(priv, D3DVSDE_BLENDWEIGHT, &gl->blend_weights);
	switch(GET_INPUT_REG_TYPE(vs->input_reg[D3DVSDE_BLENDWEIGHT].type)) {
	  case D3DVSDT_FLOAT1: fvf = D3DFVF_XYZB1; break;
	  case D3DVSDT_FLOAT2: fvf = D3DFVF_XYZB2; break;
	  case D3DVSDT_FLOAT3: fvf = D3DFVF_XYZB3; break;
	  case D3DVSDT_FLOAT4: fvf = D3DFVF_XYZB4; break;
	  default: TRACE("unhandled data format for D3DVSDE_BLEND_WEIGHT\n");
	}
      }
    }
    /* blend indices */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_BLENDINDICES].type) ) {
      if (GET_INPUT_REG_TYPE(vs->input_reg[D3DVSDE_BLENDINDICES].type) == D3DVSDT_UBYTE4) {
        GL_VertexStream8PtrStride(priv, D3DVSDE_BLENDINDICES, &gl->matrix_indices);
	if( (fvf & D3DFVF_POSITION_MASK) != D3DFVF_XYZB4 ) {
	  fvf = D3DFVF_XYZB4;
	}
	fvf |= D3DFVF_LASTBETA_UBYTE4; 
      } else {
	TRACE("invalid data format for D3DVSDE_BLENDINDICES\n");
      }
    }
     /* normal */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_NORMAL].type) ) {
      GL_VertexStream8PtrStride(priv, D3DVSDE_NORMAL, &gl->normal);
      fvf |= D3DFVF_NORMAL;
    }
    /* FIXME: psize */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_PSIZE].type) ) {
      FIXME("fixed function vertex declaration uses D3DVSDE_PSIZE -- not implemented\n");
      /* fvf |= D3DFVF_PSIZ; */
    }
    /* diffuse */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_DIFFUSE].type) ) {
      GL_VertexStream8PtrStride(priv, D3DVSDE_DIFFUSE, &gl->color[0]);
      fvf |= D3DFVF_DIFFUSE; 
    }
    /* specular */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_SPECULAR].type) ) {
      GL_VertexStream8PtrStride(priv, D3DVSDE_SPECULAR, &gl->color[1]);
      fvf |= D3DFVF_SPECULAR; 
    }
    /* textures 0-7 */
    for( i=0; i<D3DDP_MAXTEXCOORD; i++ ) {
      if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_TEXCOORD0+i].type) ) {
	GL_VertexStream8PtrStride(priv, D3DVSDE_TEXCOORD0+i, &gl->textureCoords[i]);
	switch(GET_INPUT_REG_TYPE(vs->input_reg[D3DVSDE_TEXCOORD0+i].type)) {
	case D3DVSDT_FLOAT1: fvf |= D3DFVF_TEXCOORDSIZE1(i); break;
	case D3DVSDT_FLOAT2: fvf |= D3DFVF_TEXCOORDSIZE2(i); break;
	case D3DVSDT_FLOAT3: fvf |= D3DFVF_TEXCOORDSIZE3(i); break;
	case D3DVSDT_FLOAT4: fvf |= D3DFVF_TEXCOORDSIZE4(i); break;
	default: TRACE("unhandled data format for D3DVSDE_TEXCOORD%d\n", i);
	}
	texcount++;
      }
    }
    fvf |= (texcount << D3DFVF_TEXCOUNT_SHIFT);
      
    /* FIXME: position2 -- used for vertex tweening */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_POSITION2].type) ) {
      FIXME("fixed function vertex declaration uses D3DVSDE_POSITION2 -- not implemented\n");
    }
    /* FIXME: normal2 -- used for vertex tweening */
    if( IS_INPUT_REG_ENABLED(vs->input_reg[D3DVSDE_NORMAL2].type) ) {
      FIXME("fixed function vertex declaration uses D3DVSDE_NORMAL2 -- not implemented\n");
    } 

    GL_SetupStrideFogCoordinate(fvf, gl);

    return fvf;
}

static void GL_VertexStream9PtrStride(GL_D3D_priv* priv, D3DVERTEXELEMENT9* vptr,
				      D3DDP_PTRSTRIDE* sd)
{
    BYTE streamnum = vptr->Stream;
    DWORD ofs = vptr->Offset;
    if (streamnum || (priv->glFlags & GLF_VERTEX_BUFFER)) {
      /* normal case */
      sd->dwStride = priv->d3dp->stream[streamnum].stride;
      sd->lpvData = (LPVOID)priv->d3dp->stream[streamnum].data_t->gbl.fpVidMem + ofs;
    }
    else if (priv->glFlags & GLF_STRIDED_DATA) {
      /* stream zero user pointer (Draw[Indexed]PrimitiveUP) */
      sd->dwStride = priv->sd.position.dwStride;
      sd->lpvData = priv->sd.position.lpvData + ofs;
    }
    else {
      FIXME("vertex declaration stream zero badness\n");
      sd->dwStride = priv->d3dp->stream[0].stride;
      sd->lpvData = (LPVOID)priv->d3dp->stream[0].data_t->gbl.fpVidMem + ofs;
    }
    TRACE("usage %d:%d stream %d ofs %d => %p:%ld\n",
          vptr->Usage, vptr->UsageIndex,
          vptr->Stream, vptr->Offset, sd->lpvData, sd->dwStride);
}

static DWORD
GL_VertexDeclaration9ToGLStride(GL_D3D_priv* priv, LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    D3DVERTEXELEMENT9 *vptr = (D3DVERTEXELEMENT9 *)priv->state.vertex_decl;
    DWORD fvf = 0;
    DWORD texcount = 0;
    BOOL fogc = FALSE;

    TRACE("converting vertex shader declaration to gl stride\n");
    for (; vptr->Stream != 0xff; vptr++) {
      switch (vptr->Usage) {
      case D3DDECLUSAGE_POSITION:
	switch(vptr->UsageIndex) {
	case 0:
	  GL_VertexStream9PtrStride(priv, vptr, &gl->position);
	  if (fvf & D3DFVF_POSITION_MASK) break;
	  switch(vptr->Type) {
	    case D3DDECLTYPE_FLOAT3: fvf |= D3DFVF_XYZ; break;
	    default: TRACE("unhandled data format for D3DDECLUSAGE_POSITION\n");
	  }
	  break;
	case 1:
	  /* used for vertex tweening */
	  FIXME("fixed function vertex declaration uses D3DDECLUSAGE_POSITION[1] -- not implemented\n");
	  break;
	default:
	  TRACE("unhandled index D3DDECLUSAGE_POSITION[%d]\n", vptr->UsageIndex);
	}
	break;
      case D3DDECLUSAGE_BLENDWEIGHT:
	GL_VertexStream9PtrStride(priv, vptr, &gl->blend_weights);
	fvf &= ~D3DFVF_POSITION_MASK;
	switch(vptr->Type) {
	  case D3DDECLTYPE_FLOAT1: fvf |= D3DFVF_XYZB1; break;
	  case D3DDECLTYPE_FLOAT2: fvf |= D3DFVF_XYZB2; break;
	  case D3DDECLTYPE_FLOAT3: fvf |= D3DFVF_XYZB3; break;
	  case D3DDECLTYPE_FLOAT4: fvf |= D3DFVF_XYZB4; break;
	  default: TRACE("unhandled data format for D3DDECLUSAGE_BLENDWEIGHT\n");
	}
	break;
      case D3DDECLUSAGE_BLENDINDICES:
	if (vptr->Type == D3DDECLTYPE_UBYTE4) {
	  GL_VertexStream9PtrStride(priv, vptr, &gl->matrix_indices);
	  if( (fvf & D3DFVF_POSITION_MASK) != D3DFVF_XYZB4 ) {
	    fvf = D3DFVF_XYZB4;
	  }
	  fvf |= D3DFVF_LASTBETA_UBYTE4; 
	} else {
	  TRACE("invalid data format for D3DDECLUSAGE_BLENDINDICES\n");
	}
	break;
      case D3DDECLUSAGE_NORMAL:
	switch(vptr->UsageIndex) {
	case 0:
	  GL_VertexStream9PtrStride(priv, vptr, &gl->normal);
	  fvf |= D3DFVF_NORMAL;
	  break;
	case 1:
	  /* used for vertex tweening */
	  FIXME("fixed function vertex declaration uses D3DDECLUSAGE_NORMAL[1] -- not implemented\n");
	  break;
	default:
	  TRACE("unhandled index D3DDECLUSAGE_POSITION[%d]\n", vptr->UsageIndex);
	}
	break;
      case D3DDECLUSAGE_PSIZE:
	FIXME("fixed function vertex declaration uses D3DDECLUSAGE_PSIZE -- not implemented\n");
	/* fvf |= D3DFVF_PSIZ; */
	break;
      case D3DDECLUSAGE_TEXCOORD:
	GL_VertexStream9PtrStride(priv, vptr, &gl->textureCoords[vptr->UsageIndex]);
	switch(vptr->Type) {
	case D3DDECLTYPE_FLOAT1: fvf |= D3DFVF_TEXCOORDSIZE1(vptr->UsageIndex); break;
	case D3DDECLTYPE_FLOAT2: fvf |= D3DFVF_TEXCOORDSIZE2(vptr->UsageIndex); break;
	case D3DDECLTYPE_FLOAT3: fvf |= D3DFVF_TEXCOORDSIZE3(vptr->UsageIndex); break;
	case D3DDECLTYPE_FLOAT4: fvf |= D3DFVF_TEXCOORDSIZE4(vptr->UsageIndex); break;
	default: TRACE("unhandled data format for D3DDECLUSAGE_TEXCOORD[%d]\n", vptr->UsageIndex);
	}
	texcount++;
	break;
      case D3DDECLUSAGE_TANGENT:
      case D3DDECLUSAGE_BINORMAL:
      case D3DDECLUSAGE_TESSFACTOR:
      case D3DDECLUSAGE_POSITIONT:
	FIXME("unhandled usage %d\n", vptr->Usage);
	break;
      case D3DDECLUSAGE_COLOR:
	switch(vptr->UsageIndex) {
	case 0:
	  GL_VertexStream9PtrStride(priv, vptr, &gl->color[0]);
	  fvf |= D3DFVF_DIFFUSE;
	  break;
	case 1:
	  GL_VertexStream9PtrStride(priv, vptr, &gl->color[1]);
	  fvf |= D3DFVF_SPECULAR;
	  break;
	default:
	  TRACE("unhandled index D3DDECLUSAGE_COLOR[%d]\n", vptr->UsageIndex);
	  break;
	}
	break;
      case D3DDECLUSAGE_FOG:
	GL_VertexStream9PtrStride(priv, vptr, &gl->fog_coordinate);
	fogc = TRUE;
	break;
      case D3DDECLUSAGE_DEPTH:
      case D3DDECLUSAGE_SAMPLE:
	FIXME("unhandled usage %d\n", vptr->Usage);
	break;
      default:
	FIXME("unhandled usage %d\n", vptr->Usage);
	break;
      }
    }
    fvf |= (texcount << D3DFVF_TEXCOUNT_SHIFT);

    if (!fogc) GL_SetupStrideFogCoordinate(fvf, gl);

    return fvf;
}

static DWORD
GL_VertexDeclarationToGLStride(GL_D3D_priv* priv, LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    if (priv->d3d_version >= 9)
	return GL_VertexDeclaration9ToGLStride(priv, gl);
    else
	return GL_VertexDeclaration8ToGLStride(priv, gl);
}

static void
GL_ConvertInterleavedToGLStride(DWORD fvf, LPCVOID lpVertices,
				LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    LPBYTE p = (LPBYTE)lpVertices;
    DWORD stride;
    int tex_count = D3DFVF_TEXCOUNT(fvf);
    int i;

    gl->position.lpvData = p;
    stride = D3D_GL_PositionSize(fvf) * sizeof(D3DVALUE);

    if (fvf & D3DFVF_NORMAL)
    {
	gl->normal.lpvData = p + stride;
	stride += 3 * sizeof(D3DVALUE);
    }

    if (fvf & D3DFVF_PSIZE) { 
        /* FIXME: point size, was D3DFVF_RESERVED1 before DX8 */
        stride += sizeof(D3DVALUE);
    }
      
    if (fvf & D3DFVF_DIFFUSE)
    {
	gl->color[0].lpvData = p + stride;
	stride += sizeof(D3DCOLOR);
    }

    if (fvf & D3DFVF_SPECULAR)
    {
	gl->color[1].lpvData = p + stride;
	stride += sizeof(D3DCOLOR);
	if (fvf & D3DFVF_GLCOLORORDER)
	    /* room for converted fog coordinate */
	    stride += sizeof(float);
    }

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

    gl->position.dwStride = stride;
    gl->normal.dwStride = stride;
    gl->color[0].dwStride = stride;
    gl->color[1].dwStride = stride;
    for (i=0; i < tex_count; i++)
	gl->textureCoords[i].dwStride = stride;

    GL_SetupStrideBlendWeights(fvf, gl);
    GL_SetupStrideFogCoordinate(fvf, gl);
}

#ifdef DEBUG_VERTEX_DATA
static void D3D_GL_dump_vertex(GL_D3D_priv* priv, DWORD fvf, DWORD dwIndex)
{
    LPGLDRAWPRIMITIVESTRIDEDDATA stride = &priv->stride;
    D3DVALUE *x;
    DWORD *c;
    int i;

#define STRIDE_DATA(x) \
  (LPVOID)((LPBYTE)stride->x.lpvData + dwIndex * stride->x.dwStride)

    x = STRIDE_DATA(position);
    if ((fvf & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW) {
      D3DVALUE w = 1.0/x[3];
      TRACE("  XYZRHW: %f, %f, %f, %f (%p)\n",  x[0], x[1], x[2], x[3], x);
      TRACE("  XYZ*W : %f, %f, %f, %f\n",  x[0]*w, x[1]*w, x[2]*w, w);
    } else if ((fvf & D3DFVF_POSITION_MASK) == D3DFVF_XYZGLW) {
      D3DVALUE rhw = 1.0/x[3];
      TRACE("  XYZGLW: %f, %f, %f, %f (%p)\n",  x[0], x[1], x[2], x[3], x);
      TRACE("  XYZ/W : %f, %f, %f, %f\n",  x[0]*rhw, x[1]*rhw, x[2]*rhw, x[3]);
    } else {
      TRACE("  XYZ   : %f, %f, %f (%p)\n",  x[0], x[1], x[2], x);
      /* FIXME: blend weights */
    }

    if (fvf & D3DFVF_NORMAL) {
      x = STRIDE_DATA(normal);
      TRACE("  normal: %f, %f, %f (%p)\n",  x[0], x[1], x[2], x);
    }
    if (fvf & D3DFVF_DIFFUSE) {
      c = STRIDE_DATA(color[0]);
      TRACE("  diffuse : %08lx (%p)\n", *c, c);
    }
    if (fvf & D3DFVF_SPECULAR) {
      c = STRIDE_DATA(color[1]);
      TRACE("  specular: %08lx (%p)\n", *c, c);
      if (fvf & D3DFVF_GLCOLORORDER) {
#ifdef COMPUTE_FOG_COORDINATE
        x = STRIDE_DATA(fog_coordinate);
        TRACE("  fogcoord: %f (%p)\n", *x, c);
#endif
      }
    }

    if (fvf & D3DFVF_TEXCOUNT_MASK) {
      for (i = 0; i < D3DFVF_TEXCOUNT(fvf); i++) {
        x = STRIDE_DATA(textureCoords[i]);
	switch( D3DFVF_TEXCOORDSIZE(fvf,i) ) {
	  case 4:
	    TRACE("  tex-coord %d: %f, %f, %f, %f (%p)\n", i, x[0], x[1], x[2], x[3], x); break;
	  case 3:
	    TRACE("  tex-coord %d: %f, %f, %f (%p)\n", i, x[0], x[1], x[2], x); break;
	  case 2:
	    TRACE("  tex-coord %d: %f, %f (%p)\n", i, x[0], x[1], x); break;
	  case 1:
	    TRACE("  tex-coord %d: %f (%p)\n", i, x[0], x); break;
	  case 0:
	    break;
	}
      }
    }
}
#endif

DWORD D3D_GL_setup_fixed(GL_D3D_priv* priv, LPVOID data1, 
                         const WORD* indices, GLenum pt, DWORD dwCount, BOOL indexed)
{
    DWORD fvf;
    DWORD dwIndexBase;
    DWORD dwVStart, dwVCount;

    fvf = priv->dp2_dwVertexType;
    dwIndexBase = indexed ? ((LPD3DHAL_DP2STARTVERTEX)data1)->wVStart : 0;
    dwVStart = priv->dp2_dwVertexOffset;
    dwVCount = priv->dp2_dwVertexLength;

    /* Compute the initial GL stride. */
    if (priv->glFlags & GLF_VERTEX_DECLARATION)
    {
	fvf = GL_VertexDeclarationToGLStride(priv, &priv->stride);
	if (GL_IsDirectlyRenderable(priv, fvf)) {
	    /* FIXME: call PreRender for all input streams, as is done
	     * in vprogram.c (and call PostRender in finish_fixed) */
	    TRACE("potential fast path taken for vertex declaration\n");
	}
	else {
	    TRACE("slow path taken for vertex declaration\n");
	}
    }
    else if (priv->glFlags & GLF_STRIDED_DATA)
    {
	GL_ConvertD3DStrideToGLStride(fvf, &priv->sd, &priv->stride);
    }
    else if (priv->glFlags & GLF_VERTEX_BUFFER)
    {
	const struct GL_D3D_DriverCallbacks *cb = &priv->callbacks;

	if (cb->D3dVbGetConvertedFVF)
	{
	    DWORD converted_fvf;
	    BOOL do_conversion;

	    converted_fvf = cb->D3dVbGetConvertedFVF(priv->dp2_VertexBuffer);

	    do_conversion = GL_IsDirectlyRenderable(priv, converted_fvf);

	    if (do_conversion)
	    {
		if (GL_CHECK_NEW_TNL(fvf, converted_fvf))
		    priv->glFlags |= GLF_NEW_TNL_MODE;

		fvf = priv->dp2_dwVertexType = converted_fvf;
	    }

	    cb->D3dVbPreRender(priv->dp2_VertexBuffer,
			       &priv->stride,
			       do_conversion,
			       dwVStart,
			       dwVCount,
			       indices, dwCount,
			       dwIndexBase);
	}
	else
	{
	    GL_ConvertInterleavedToGLStride(fvf,
					    (LPVOID)priv->dp2_VertexBuffer->lpGbl->fpVidMem,
					    &priv->stride);
	}
    }
    else
    {
	GL_ConvertInterleavedToGLStride(fvf,
					priv->dp2_lpVertices,
					&priv->stride);
    }

    TRACE("fvf=%lx, ibase=%ld, vstart=%ld, vcount=%ld\n", fvf, dwIndexBase, dwVStart, dwVCount);
    if (pt == GL_POINTS)
    {
        /* FIXME: debug dumps for points */
    }
    else if (indexed)
    {
	LPD3DHAL_DP2STARTVERTEX dat = (LPD3DHAL_DP2STARTVERTEX)data1;

	TRACE("start=%d, count=%ld [indexed]\n", dat->wVStart, dwCount);
#ifdef DEBUG_VERTEX_DATA
	{
	    DWORD i;
	    for (i=0; i<dwCount; i++) {
		WORD idx = indices[i];
		TRACE(" index%03ld: %03d\n", i, idx);
		D3D_GL_dump_vertex(priv, fvf, dwIndexBase + idx);
	    }
	}
#endif
    }
    else
    {
	LPD3DHAL_DP2STARTVERTEX dat = (LPD3DHAL_DP2STARTVERTEX)data1;

	TRACE("start=%d, count=%ld\n", dat->wVStart, dwCount);
#ifdef DEBUG_VERTEX_DATA
	{
	    DWORD i;
	    for (i=0; i<dwCount; i++) {
		D3D_GL_dump_vertex(priv, fvf, dwVStart + dat->wVStart + i);
	    }
	}
#endif
    }

    /* Disable any active vertex program */
    if (priv->isVertexProgramEnabled) {
	GL_VP_disable(priv);
    }

    /* We have the raw D3D arrays in the GL stride structure.
     * Now we have to replace the arrays that are not supported by OpenGL. */

    /* FIXME: recompute vertex materials only on (NEW_LIGHTING | NEW_TNL_MODE) ? */
    fvf = GL_fixup_vertex_materials(priv, fvf, &priv->stride);
    GL_fixup_position_array(priv, fvf, dwVStart, dwVCount,
			    &priv->stride, indices, dwCount, dwIndexBase);
    GL_fixup_color_arrays(priv, fvf, dwVStart, dwVCount,
			  &priv->stride, indices, dwCount, dwIndexBase);

    GL_load_arrays(priv, fvf, dwIndexBase, &priv->stride);

    GL_setup_transforms(priv, fvf);
    GL_setup_lighting(priv, fvf);
    GL_setup_linear_fog(priv,
			priv->material_flags & GLMF_FOG_COORDINATE,
			priv->state.renderstate[D3DRS_FOGENABLE]);

    priv->glFlags &= ~(GLF_NEW_TNL_MODE | GLF_NEW_LIGHTING);

    CHECK_GL_ERROR("setup_fixed");
    return fvf;
}

void D3D_GL_finish_fixed(GL_D3D_priv* priv)
{
    if (priv->glFlags & GLF_VERTEX_DECLARATION) {
	/* FIXME */
    }
    else if ((priv->glFlags & GLF_VERTEX_BUFFER)
	&& priv->callbacks.D3dVbPostRender)
	priv->callbacks.D3dVbPostRender(priv->dp2_VertexBuffer);
}
