/*
 * Direct3D9 vertex shaders
 * Copyright (c) 2003, TransGaming Technologies Inc.
 */

#include <stdio.h>
#include "config.h"

#include "winbase.h"
#include "d3d9.h"
#include "wine/debug.h"

#include "d3d9_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

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

/* vertex shader declaration macros from d3d8types.h,
 * so we can convert from d3d9 decls to d3d8 decls. */

typedef enum _D3DVSD_TOKENTYPE {
  D3DVSD_TOKEN_NOP         = 0,
  D3DVSD_TOKEN_STREAM      = 1,
  D3DVSD_TOKEN_STREAMDATA  = 2,
  D3DVSD_TOKEN_TESSELLATOR = 3,
  D3DVSD_TOKEN_CONSTMEM    = 4,
  D3DVSD_TOKEN_EXT         = 5,
  D3DVSD_TOKEN_END         = 7,
  D3DVSD_FORCE_DWORD = 0x7fffffff
} D3DVSD_TOKENTYPE;

#define D3DVSD_TOKENTYPESHIFT    29
#define D3DVSD_TOKENTYPEMASK    (0x7 << D3DVSD_TOKENTYPESHIFT)

#define D3DVSD_STREAMNUMBERSHIFT  0
#define D3DVSD_STREAMNUMBERMASK (0xf << D3DVSD_STREAMNUMBERSHIFT)

#define D3DVSD_STREAMTESSSHIFT   28
#define D3DVSD_STREAMTESSMASK   (0x1 << D3DVSD_STREAMTESSSHIFT)

#define D3DVSD_DATALOADTYPESHIFT 28
#define D3DVSD_DATALOADTYPEMASK (0x1 << D3DVSD_DATALOADTYPESHIFT)

#define D3DVSD_DATATYPESHIFT     16
#define D3DVSD_DATATYPEMASK     (0xf << D3DVSD_DATATYPESHIFT)

#define D3DVSD_SKIPCOUNTSHIFT    16
#define D3DVSD_SKIPCOUNTMASK    (0xf << D3DVSD_SKIPCOUNTSHIFT)

#define D3DVSD_VERTEXREGSHIFT     0
#define D3DVSD_VERTEXREGMASK    (0x1f << D3DVSD_VERTEXREGSHIFT)

#define D3DVSD_VERTEXREGINSHIFT  20
#define D3DVSD_VERTEXREGINMASK  (0xf << D3DVSD_VERTEXREGINSHIFT)

#define D3DVSD_CONSTCOUNTSHIFT   25
#define D3DVSD_CONSTCOUNTMASK   (0xf << D3DVSD_CONSTCOUNTSHIFT)

#define D3DVSD_CONSTADDRESSSHIFT  0
#define D3DVSD_CONSTADDRESSMASK (0x7f << D3DVSD_CONSTADDRESSSHIFT)

#define D3DVSD_CONSTRSSHIFT      16
#define D3DVSD_CONSTRSMASK      (0x1fff << D3DVSD_CONSTREGSHIFT)

#define D3DVSD_EXTCOUNTSHIFT     24
#define D3DVSD_EXTCOUNTMASK     (0x1f << D3DVSD_EXTCOUNTSHIFT)

#define D3DVSD_EXTINFOSHIFT       0
#define D3DVSD_EXTINFOMASK      (0xffffff << D3DVSD_EXTINFOSHIFT)

#define D3DVSD_MAKETOKENTYPE(tokenType) ((tokenType << D3DVSD_TOKENTYPESHIFT) & D3DVSD_TOKENTYPEMASK)

#define D3DVSD_STREAM(streamNumber) (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAM) | (streamNumber))
#define D3DVSD_STREAM_TESS() D3DVSD_STREAM(D3DVSD_STREAMTESSMASK)
#define D3DVSD_REG(vertexRegister, regType) \
  (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAMDATA) | ((regType) << D3DVSD_DATATYPESHIFT) | (vertexRegister))
#define D3DVSD_SKIP(skipCount) \
  (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAMDATA) | D3DVSD_DATALOADTYPEMASK | ((skipCount) << D3DVSD_SKIPCOUNTSHIFT))
#define D3DVSD_CONST(constAddress, constCount) \
  (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_CONSTMEM) | ((constCount) << D3DVSD_CONSTCOUNTSHIFT) | (constAddress))
/* FIXME: D3DVSD_TESS* */
#define D3DVSD_END() 0xffffffff
#define D3DVSD_NOP() 0x00000000

#define D3DVSDT_FLOAT1   0x00
#define D3DVSDT_FLOAT2   0x01
#define D3DVSDT_FLOAT3   0x02
#define D3DVSDT_FLOAT4   0x03
#define D3DVSDT_D3DCOLOR 0x04
#define D3DVSDT_UBYTE4   0x05
#define D3DVSDT_SHORT2   0x06
#define D3DVSDT_SHORT4   0x07

#define D3DVSDE_POSITION     0
#define D3DVSDE_BLENDWEIGHT  1
#define D3DVSDE_BLENDINDICES 2
#define D3DVSDE_NORMAL       3
#define D3DVSDE_PSIZE        4
#define D3DVSDE_DIFFUSE      5
#define D3DVSDE_SPECULAR     6
#define D3DVSDE_TEXCOORD0    7
#define D3DVSDE_TEXCOORD1    8
#define D3DVSDE_TEXCOORD2    9
#define D3DVSDE_TEXCOORD3   10
#define D3DVSDE_TEXCOORD4   11
#define D3DVSDE_TEXCOORD5   12
#define D3DVSDE_TEXCOORD6   13
#define D3DVSDE_TEXCOORD7   14
#define D3DVSDE_POSITION2   15
#define D3DVSDE_NORMAL2     16


static DWORD dump_unrecognised_instruction(const DWORD* program)
{
    DWORD length = 1;

    TRACE("unrecognized ins: %08lX\n", *program);
    program++;

    while (*program & 0x80000000)
    {
	TRACE("unrecognized ins: %08lX\n", *program);
        program++;
	length++;
    }

    /*if (TRACE_ON(ddraw)) DPRINTF("dumped %d unrecognized instructions\n", length);*/
    return length;
}

static BOOL is_comment(DWORD opcode) {
    return ((opcode & D3DSI_OPCODE_MASK) == D3DSIO_COMMENT );
}

static DWORD dump_comment(const DWORD* program)
{
    DWORD dwordsize = (*program & D3DSI_COMMENTSIZE_MASK) >> D3DSI_COMMENTSIZE_SHIFT;
#if 0
    int len = dwordsize *sizeof(DWORD) + 1;
    char comment[len]; 

    program++;
    strncpy( (char *)comment, (char *)program, len-1 );
    comment[len-1] = '\0'; 
    if (TRACE_ON(ddraw)) DPRINTF("COMMENT: %ld dwords = '%s'\n", dwordsize, (char *)comment);
#endif
    return (dwordsize + 1); /* include the comment opcode itself */
}

static BOOL is_recognised_opcode(DWORD opcode)
{
    static const BYTE opcodes[] =
	{
	    [D3DSIO_NOP] = TRUE,
	    [D3DSIO_MOV] = TRUE,
	    [D3DSIO_ADD] = TRUE,
	    [D3DSIO_SUB] = TRUE,
	    [D3DSIO_MAD] = TRUE,
	    [D3DSIO_MUL] = TRUE,
	    [D3DSIO_RCP] = TRUE,
	    [D3DSIO_RSQ] = TRUE,
	    [D3DSIO_DP3] = TRUE,
	    [D3DSIO_DP4] = TRUE,
	    [D3DSIO_MIN] = TRUE,
	    [D3DSIO_MAX] = TRUE,
	    [D3DSIO_SLT] = TRUE,
	    [D3DSIO_SGE] = TRUE,
	    [D3DSIO_EXP] = TRUE,
	    [D3DSIO_LOG] = TRUE,
	    [D3DSIO_LIT] = TRUE,
	    [D3DSIO_DST] = TRUE,
	    [D3DSIO_LRP] = TRUE,
	    [D3DSIO_FRC] = TRUE,
	    [D3DSIO_M4x4] = TRUE,
	    [D3DSIO_M4x3] = TRUE,
	    [D3DSIO_M3x4] = TRUE,
	    [D3DSIO_M3x3] = TRUE,
	    [D3DSIO_M3x2] = TRUE,
	    [D3DSIO_EXPP] = TRUE,
	    [D3DSIO_LOGP] = TRUE
	};

    if (opcode >= sizeof(opcodes)) return FALSE;

    return opcodes[opcode];
}

static void dump_opcode(DWORD opcode)
{
    static const char * const opcode_names[] =
	{
	    [D3DSIO_NOP] = "NOP",
	    [D3DSIO_MOV] = "MOV",
	    [D3DSIO_ADD] = "ADD",
	    [D3DSIO_SUB] = "SUB",
	    [D3DSIO_MAD] = "MAD",
	    [D3DSIO_MUL] = "MUL",
	    [D3DSIO_RCP] = "RCP",
	    [D3DSIO_RSQ] = "RSQ",
	    [D3DSIO_DP3] = "DP3",
	    [D3DSIO_DP4] = "DP4",
	    [D3DSIO_MIN] = "MIN",
	    [D3DSIO_MAX] = "MAX",
	    [D3DSIO_SLT] = "SLT",
	    [D3DSIO_SGE] = "SGE",
	    [D3DSIO_EXP] = "EXP",
	    [D3DSIO_LOG] = "LOG",
	    [D3DSIO_LIT] = "LIT",
	    [D3DSIO_DST] = "DST",
	    [D3DSIO_LRP] = "LRP",
	    [D3DSIO_FRC] = "FRC",
	    [D3DSIO_M4x4] = "M4x4",
	    [D3DSIO_M4x3] = "M4x3",
	    [D3DSIO_M3x4] = "M3x4",
	    [D3DSIO_M3x3] = "M3x3",
	    [D3DSIO_M3x2] = "M3x2",
	    [D3DSIO_EXPP] = "EXPP",
	    [D3DSIO_LOGP] = "LOGP"
	};

    if (TRACE_ON(ddraw)) DPRINTF(opcode_names[opcode]);
}

static void dump_register(DWORD reg)
{
    static const char * const rastout_names[] = { "oPos", "oFog", "oPts" };

    DWORD regnum = reg & D3DSP_REGNUM_MASK;

    switch ((reg & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT)
    {
    case D3DSPR_TEMP:
	if (TRACE_ON(ddraw)) DPRINTF("r%lu", regnum);
	break;

    case D3DSPR_INPUT:
	if (TRACE_ON(ddraw)) DPRINTF("v%lu", regnum);
	break;

    case D3DSPR_CONST:
	/* XXX What is the correct interpretation of regnum when a relative
	 * offset is used?
	 * GL_NV_vertex_program uses the range -64 to 63.
	 */
	if (TRACE_ON(ddraw)) DPRINTF("c[%s%lu]",
		(reg & D3DVS_ADDRMODE_RELATIVE) ? "a0.x + " : "",
		regnum);
	break;

    case D3DSPR_ADDR:
	if (TRACE_ON(ddraw)) DPRINTF("a%lu", regnum);
	break;

    case D3DSPR_RASTOUT:
	if (TRACE_ON(ddraw)) DPRINTF("%s", rastout_names[regnum]);
	break;

    case D3DSPR_ATTROUT:
	if (TRACE_ON(ddraw)) DPRINTF("oD%lu", regnum);
	break;

    case D3DSPR_TEXCRDOUT:
	if (TRACE_ON(ddraw)) DPRINTF("oT%lu", regnum);
	break;
    }
}

static void dump_writemask(DWORD param)
{
    if ((param & D3DSP_WRITEMASK_ALL) != D3DSP_WRITEMASK_ALL)
    {
	if (TRACE_ON(ddraw)) DPRINTF(".");

	if (param & D3DSP_WRITEMASK_0) if (TRACE_ON(ddraw)) DPRINTF("x");
	if (param & D3DSP_WRITEMASK_1) if (TRACE_ON(ddraw)) DPRINTF("y");
	if (param & D3DSP_WRITEMASK_2) if (TRACE_ON(ddraw)) DPRINTF("z");
	if (param & D3DSP_WRITEMASK_3) if (TRACE_ON(ddraw)) DPRINTF("w");
    }
}

static void dump_output_parameter(DWORD param)
{
    dump_register(param);
    dump_writemask(param);
}

#define SWIZZLE_NONE 0xE4
#define SWIZZLE_X(x) ((x) & 0x03)
#define SWIZZLE_Y(x) (((x) & 0x0C) >> 2)
#define SWIZZLE_Z(x) (((x) & 0x30) >> 4)
#define SWIZZLE_W(x) (((x) & 0xC0) >> 6)

static void dump_swizzle(DWORD param)
{
    static const char components[] = "xyzw";

    DWORD swizzle = (param & D3DVS_SWIZZLE_MASK) >> D3DVS_SWIZZLE_SHIFT;

    if (swizzle == SWIZZLE_NONE) return;

    if (SWIZZLE_X(swizzle) == SWIZZLE_Y(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_Z(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_W(swizzle))
    {
	if (TRACE_ON(ddraw)) DPRINTF(".%c", components[SWIZZLE_X(swizzle)]);
    }
    else
    {
	if (TRACE_ON(ddraw)) DPRINTF(".%c%c%c%c",
		components[SWIZZLE_X(swizzle)],
		components[SWIZZLE_Y(swizzle)],
		components[SWIZZLE_Z(swizzle)],
		components[SWIZZLE_W(swizzle)]);
    }
}

static void dump_input_parameter(DWORD param)
{
    if ((param & D3DSP_SRCMOD_MASK) == D3DSPSM_NEG) if (TRACE_ON(ddraw)) DPRINTF("-");
    dump_register(param);
    dump_swizzle(param);
}

static DWORD dump_constant_define(const DWORD *program)
{
    if(TRACE_ON(ddraw)) {
	DPRINTF("DEF ");
	dump_register(program[1]);
	DPRINTF(" %1.8f, %1.8f, %1.8f, %1.8f\n", ((float*)program)[2], ((float*)program)[3], ((float*)program)[4], ((float*)program)[5]);
    }
    return 6;
}

static DWORD dump_declaration_instruction(const DWORD* program)
{
    static const char * const usage_names[] =
	{
	    [D3DDECLUSAGE_POSITION]	= "POSITION",
	    [D3DDECLUSAGE_BLENDWEIGHT]	= "BLENDWEIGHT",
	    [D3DDECLUSAGE_BLENDINDICES]	= "BLENDINDICES",
	    [D3DDECLUSAGE_NORMAL]	= "NORMAL",
	    [D3DDECLUSAGE_PSIZE]	= "PSIZE",
	    [D3DDECLUSAGE_TEXCOORD]	= "TEXCOORD",
	    [D3DDECLUSAGE_TANGENT]	= "TANGENT",
	    [D3DDECLUSAGE_BINORMAL]	= "BINORMAL",
	    [D3DDECLUSAGE_TESSFACTOR]	= "TESSFACTOR",
	    [D3DDECLUSAGE_POSITIONT]	= "POSITIONT",
	    [D3DDECLUSAGE_COLOR]	= "COLOR",
	    [D3DDECLUSAGE_FOG]		= "FOG",
	    [D3DDECLUSAGE_DEPTH]	= "DEPTH",
	    [D3DDECLUSAGE_SAMPLE]	= "SAMPLE"
	};

    if (TRACE_ON(ddraw)) {
	DPRINTF("DCL_%s", usage_names[(program[1] & D3DSP_DCL_USAGE_MASK) >> D3DSP_DCL_USAGE_SHIFT]);
	DPRINTF("[%ld] ", (program[1] & D3DSP_DCL_USAGEINDEX_MASK) >> D3DSP_DCL_USAGEINDEX_SHIFT);
	dump_output_parameter(program[2]);
	DPRINTF("\n");
    }
    return 3;
}

static DWORD num_opcode_operands(DWORD opcode)
{
    static const BYTE num_params[] =
	{
	    [D3DSIO_NOP] = 0,
	    [D3DSIO_MOV] = 2,
	    [D3DSIO_ADD] = 3,
	    [D3DSIO_SUB] = 3,
	    [D3DSIO_MAD] = 4,
	    [D3DSIO_MUL] = 3,
	    [D3DSIO_RCP] = 2,
	    [D3DSIO_RSQ] = 2,
	    [D3DSIO_DP3] = 3,
	    [D3DSIO_DP4] = 3,
	    [D3DSIO_MIN] = 3,
	    [D3DSIO_MAX] = 3,
	    [D3DSIO_SLT] = 3,
	    [D3DSIO_SGE] = 3,
	    [D3DSIO_EXP] = 2,
	    [D3DSIO_LOG] = 2,
	    [D3DSIO_LIT] = 2,
	    [D3DSIO_DST] = 3,
	    [D3DSIO_FRC] = 2,
	    [D3DSIO_M4x4] = 3,
	    [D3DSIO_M4x3] = 3,
	    [D3DSIO_M3x4] = 3,
	    [D3DSIO_M3x3] = 3,
	    [D3DSIO_M3x2] = 3,
	    [D3DSIO_EXPP] = 2,
	    [D3DSIO_LOGP] = 2
	};

    return num_params[opcode];
}

static DWORD dump_instruction(const DWORD* program)
{
    if (is_recognised_opcode(*program)) {
	DWORD i;
	DWORD num_params = num_opcode_operands(program[0]);

	dump_opcode(*program++);

	if (TRACE_ON(ddraw)) DPRINTF(" ");

	if (num_params > 0) dump_output_parameter(*program++);

	for( i=1; i<num_params; i++ ) 
	{
	    if (TRACE_ON(ddraw)) DPRINTF(", ");
	    dump_input_parameter(*program++);
	}

	if (TRACE_ON(ddraw)) DPRINTF("\n");

	return num_params + 1;
    } 
    else if (is_comment(*program)) {
	return dump_comment(program);
    }
    else if (*program == D3DSIO_DEF) {
	return dump_constant_define(program);
    }
    else if (*program == D3DSIO_DCL) {
	return dump_declaration_instruction(program);
    }
    else {
	return dump_unrecognised_instruction(program);
    }
}

static BOOL check_program_version_tag(DWORD version)
{
    /* check the version tag */
    if (HIWORD(version) != 0xFFFE)
    {
	WARN("vertex program has no version tag\n");
	return FALSE;
    }

    /* 1.0, 1.1 are supported. */
    if (D3DSHADER_VERSION_MAJOR(version) != 1
	|| D3DSHADER_VERSION_MINOR(version) > 1)
    {
	WARN("unknown vertex program version: %ld.%ld\n",
	     D3DSHADER_VERSION_MAJOR(version),
	     D3DSHADER_VERSION_MINOR(version));
	return FALSE;
    }

    return TRUE;
}

static void dump_version_tag(DWORD version)
{
    if (TRACE_ON(ddraw)) DPRINTF("vs.%ld.%ld\n", D3DSHADER_VERSION_MAJOR(version),
	    D3DSHADER_VERSION_MINOR(version));
}

static UINT dump_vertex_program(const DWORD* program)
{
    const DWORD *start = program;
    if (!check_program_version_tag(program[0])) return 0;

    dump_version_tag(program[0]);

    program++;

    while (*program != D3DVS_END())
    {
	program += dump_instruction(program);
    }
    // add one to count the END token
    return (program-start+1);
}

static DWORD hdr_op_len(const DWORD* opcode)
{
    switch (*opcode & D3DSI_OPCODE_MASK) {
    case D3DSIO_COMMENT:
	return ((*opcode & D3DSI_COMMENTSIZE_MASK) >> D3DSI_COMMENTSIZE_SHIFT) + 1;
    case D3DSIO_DEF:
	return 6;
    case D3DSIO_DCL:
	return 3;
    default:
	return 0;
    }
}

#if 0
/***************************************************************************
 * Implementation of D3D VertexShader API funtions
 ***************************************************************************/

HRESULT WINAPI Direct3DDevice9_DeleteVertexShader(LPDIRECT3DDEVICE9 iface,
						  DWORD Handle)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_DELETEVERTEXSHADER, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;
  VertexShader* vs;

  TRACE("(%p)->(0x%lx)\n", iface, Handle);

  if (!IS_VS(Handle)) return D3DERR_INVALIDCALL;

  data.dwHandle = Handle;
  if (dx8_hal)
    This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);

  vs = VS_FROM_HANDLE(Handle);

  /* take the vertex shader out of the linked list */
  if (vs->prev) {
    vs->prev->next = vs->next;
  } else { /* if there is no previous, this must be head */
    This->vshaders = vs->next;
  }
  if (vs->next) {
    vs->next->prev = vs->prev;
  }
  
  if( vs->pDeclaration ) {
    HeapFree(buffer_heap, 0, (void *)vs->pDeclaration);
  }
  if( vs->pFunction ) {
    HeapFree(buffer_heap, 0, (void *)vs->pFunction);
  }
  HeapFree(GetProcessHeap(), 0, vs);

  return D3D_OK;
}

/* ------------------------------------------------------------------------
 * GetVertexShaderDeclaration
 * Handle[in] - the vertex shader we want the declarations from
 * pData[in] - application allocated buffer to place declaration into
 * pSizeOfData[in/out] - size of buffer in bytes, if this is less than
 *   the required size, the required size is placed in this parameter.
 * ------------------------------------------------------------------------
 */
HRESULT WINAPI Direct3DDevice9_GetVertexShaderDeclaration(LPDIRECT3DDEVICE9 iface,
							  DWORD Handle,
							  void* pData,
							  DWORD* pSizeOfData)
{
  VertexShader *vs;
  /* ICOM_THIS(IDirect3DDevice9Impl, iface); */
  TRACE("(%p)->(0x%lx,%p,%p)\n", iface, Handle, pData, pSizeOfData);

  /* check if a valid handle */
  if( !IS_VS(Handle) ) {
    return D3DERR_INVALIDCALL;
  }
  vs = VS_FROM_HANDLE(Handle);

  /* this is a request to get the size of buffer needed for declaration */
  if( pData == NULL ) {
    *pSizeOfData = vs->DeclarationLength;
    return D3D_OK;
  }	
	
  /* check if provided buffer (pData) is large enough */
  if (vs->DeclarationLength > (*pSizeOfData) ) {
    *pSizeOfData = vs->DeclarationLength;
    return D3DERR_MOREDATA;
  }

  /* copy the declaration block into the provided buffer */
  memcpy( pData, vs->pDeclaration, vs->DeclarationLength );
  return D3D_OK;
}

/* --------------------------------------------------------------------------
 * GetVertexShaderFunction
 * Handle[in] - the vertex shader we want the declarations from
 * pData[in] - application allocated buffer to place declaration into
 * pSizeOfData[in/out] - size of buffer in bytes, if this is less than
 *   the required size, the required size is placed in this parameter.
 * --------------------------------------------------------------------------
 */
HRESULT WINAPI Direct3DDevice9_GetVertexShaderFunction(LPDIRECT3DDEVICE9 iface,
						       DWORD Handle,
						       void* pData,
						       DWORD* pSizeOfData)
{
  VertexShader *vs;
  /* ICOM_THIS(IDirect3DDevice9Impl, iface); */
  TRACE("(%p)->(0x%lx,%p,%p)\n", iface, Handle, pData, pSizeOfData);

  /* check if a valid handle */
  if( !IS_VS(Handle) ) {
    return D3DERR_INVALIDCALL;
  }
  vs = VS_FROM_HANDLE(Handle);

  /* this is a request to get the size of buffer needed for the function */
  if( pData == NULL ) {
    *pSizeOfData = vs->FunctionLength;
    return D3D_OK;
  }	
	
  /* check if provided buffer (pData) is large enough */
  if (vs->FunctionLength > (*pSizeOfData) ) {
    *pSizeOfData = vs->FunctionLength;
    return D3DERR_MOREDATA;
  }

  /* copy the function block into the provided buffer */
  memcpy( pData, vs->pFunction, vs->FunctionLength );
  return D3D_OK;
}
#endif

static HRESULT WINAPI Direct3DVertexDeclaration9_QueryInterface(LPDIRECT3DVERTEXDECLARATION9 iface,
								REFIID riid,
								LPVOID* obj)
{
  ICOM_THIS(IDirect3DVertexDeclaration9Impl, iface);
  TRACE("(%p)->(%s,%p)\n", iface, debugstr_guid(riid), obj);
  if (IsEqualGUID(&IID_IUnknown, riid) ||
      IsEqualGUID(&IID_IDirect3DVertexDeclaration9, riid)) {
    This->ref++;
    *obj = This;
    return S_OK;
  }
  else {
    FIXME("(%p)->(%s): no interface", iface, debugstr_guid(riid));
    return E_NOINTERFACE;
  }
}

static ULONG WINAPI Direct3DVertexDeclaration9_AddRef(LPDIRECT3DVERTEXDECLARATION9 iface)
{
  ICOM_THIS(IDirect3DVertexDeclaration9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref;
}

static ULONG WINAPI Direct3DVertexDeclaration9_Release(LPDIRECT3DVERTEXDECLARATION9 iface)
{
  ICOM_THIS(IDirect3DVertexDeclaration9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_DELETEVERTEXSHADERDECL, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;

  TRACE("(%p)->()\n", iface);
  if (--This->ref) return This->ref;
  if (This->vs) {
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_DELETEVERTEXSHADER, 0, {1} };
    D3DHAL_DP2VERTEXSHADER data;
    data.dwHandle = VS_TO_HANDLE(This->vs);
    This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
    HeapFree(GetProcessHeap(), 0, (void *)This->vs->pDeclaration);
  }
  if (dx9_hal) {
    data.dwHandle = (DWORD)This->pDeclaration;
    This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  }
  HeapFree(GetProcessHeap(), 0, (void *)This->pDeclaration);
  HeapFree(GetProcessHeap(), 0, This);
  return 0;
}

static ICOM_VTABLE(IDirect3DVertexDeclaration9) D3DVD9_Vtbl = {
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DVertexDeclaration9_QueryInterface,
  Direct3DVertexDeclaration9_AddRef,
  Direct3DVertexDeclaration9_Release,
  (void*)0xdead9004, /* GetDevice */
  (void*)0xdead9005  /* GetDeclaration */
};

HRESULT WINAPI Direct3DDevice9_CreateVertexDeclaration(LPDIRECT3DDEVICE9 iface,
						       CONST D3DVERTEXELEMENT9* pVertexElements,
						       IDirect3DVertexDeclaration9** ppDecl)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CREATEVERTEXSHADERDECL, 0, {1} };
  D3DHAL_DP2CREATEVERTEXSHADERDECL data;
  IDirect3DVertexDeclaration9Impl* pDecl;
  CONST D3DVERTEXELEMENT9* ptr;
  UINT len = 0, streams = 0;
  DWORD fvf = 0, pos = 0, tex = 0;
  WORD last_stream = 0xFF;
  BOOL invalid = FALSE;

  TRACE("(%p)->(%p,%p)\n", iface, pVertexElements, ppDecl);
  *ppDecl = NULL;

  /* validate input data */
  for (ptr = pVertexElements; ptr->Stream != 0xFF; ptr++) {
    TRACE(" stream %d offset %d type %d method %d usage %d index %d\n",
          ptr->Stream, ptr->Offset, ptr->Type, ptr->Method, ptr->Usage, ptr->UsageIndex);
    if (ptr->Type > MAXD3DDECLTYPE) {
      WARN("invalid type encountered\n");
      invalid = TRUE;
    }
  }

  /* if (invalid) return D3DERR_INVALIDCALL; */

  pDecl = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DVertexDeclaration9Impl));
  pDecl->lpVtbl = &D3DVD9_Vtbl;
  pDecl->parent = This;
  pDecl->ref = 1;

  for (ptr = pVertexElements; ptr->Stream != 0xFF; ptr++,len++) {
    /* if this is a vertex shader for the fixed-function pipeline,
     * life becomes easier when it has a FVF to refer to */
    switch (ptr->Usage) {
    case D3DDECLUSAGE_POSITION:
      switch (ptr->Type) {
      case D3DDECLTYPE_FLOAT3: pos = max(pos, 1); break;
      case D3DDECLTYPE_FLOAT4: fvf |= D3DFVF_XYZW; break;
      default: /* ? */ break;
      }
      break;
    case D3DDECLUSAGE_BLENDWEIGHT:
      switch (ptr->Type) {
      case D3DDECLTYPE_FLOAT1: pos = max(pos, 3); break;
      case D3DDECLTYPE_FLOAT2: pos = max(pos, 4); break;
      case D3DDECLTYPE_FLOAT3: pos = max(pos, 5); break;
      case D3DDECLTYPE_FLOAT4: pos = max(pos, 6); break;
      default: /* ? */ break;
      }
      break;
    case D3DDECLUSAGE_BLENDINDICES:
      switch (ptr->Type) {
      case D3DDECLTYPE_UBYTE4: fvf |= D3DFVF_LASTBETA_UBYTE4; break;
      case D3DDECLTYPE_D3DCOLOR: fvf |= D3DFVF_LASTBETA_D3DCOLOR; break;
      case D3DDECLTYPE_FLOAT1: pos = 7; break;
      default: /* ? */ break;
      }
      break;
    case D3DDECLUSAGE_NORMAL: fvf |= D3DFVF_NORMAL; break;
    case D3DDECLUSAGE_PSIZE: fvf |= D3DFVF_PSIZE; break;
    case D3DDECLUSAGE_TEXCOORD: tex = max(tex, ptr->UsageIndex+1); break;
    case D3DDECLUSAGE_POSITIONT: fvf |= D3DFVF_XYZRHW; break;
    case D3DDECLUSAGE_COLOR: fvf |= D3DFVF_DIFFUSE << ptr->UsageIndex; break;
    default: /* ? */ break;
    }
    if (ptr->Stream != last_stream) {
      streams++;
      last_stream = ptr->Stream;
    }
  }
  len++; /* count the END() token */
  if ((pos > 2) && (fvf & (D3DFVF_LASTBETA_UBYTE4 | D3DFVF_LASTBETA_D3DCOLOR))) pos++;
  if ((fvf & D3DFVF_POSITION_MASK) && pos) {
    FIXME("unexpected position specification in vertex declaration\n");
    pos = 0;
  }
  fvf |= (pos << 1) | (tex << D3DFVF_TEXCOUNT_SHIFT);
  pDecl->FVF = fvf;
  TRACE(" declaration length=%d elements\n", len);
  pDecl->DeclarationLength = len;
  pDecl->pDeclaration = HeapAlloc(buffer_heap, 0, len * sizeof(D3DVERTEXELEMENT9));
  memcpy((void *)pDecl->pDeclaration, (void *)pVertexElements, len * sizeof(D3DVERTEXELEMENT9));
  pDecl->Streams = streams;

  TRACE(" => %p\n", pDecl);

  *ppDecl = (IDirect3DVertexDeclaration9*)pDecl;

  data.dwNumVertexElements = pDecl->DeclarationLength;
  if (dx9_hal) {
    data.dwHandle = (DWORD)pDecl->pDeclaration;
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data),
			  pDecl->pDeclaration, pDecl->DeclarationLength * sizeof(D3DVERTEXELEMENT9));
  } else {
    return D3D_OK;
  }
}

HRESULT WINAPI Direct3DDevice9_SetVertexDeclaration(LPDIRECT3DDEVICE9 iface,
						    IDirect3DVertexDeclaration9* pDecl)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADERDECL, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;

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

  if (pDecl == This->current->vertex_decl && This->current->dirty_vdecl) return D3D_OK;
  if (pDecl) IDirect3DVertexDeclaration9_AddRef(pDecl);
  if (This->current->vertex_decl) IDirect3DVertexDeclaration9_Release(This->current->vertex_decl);
  This->current->vertex_decl = pDecl;
  This->current->dirty_vdecl = TRUE;
  if (!This->current->state_block) {
    This->d3dp.vertex_fvf = ((IDirect3DVertexDeclaration9Impl*)pDecl)->FVF;
    This->d3dp.vertex_shader = NULL; /* force reload */
  }
  else
    This->current->dirty_fvf = FALSE;

  data.dwHandle = pDecl ? (DWORD)((IDirect3DVertexDeclaration9Impl*)pDecl)->pDeclaration : 0;
  if (dx9_hal) {
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  } else {
    return D3D_OK;
  }
}

HRESULT WINAPI Direct3DDevice9_GetVertexDeclaration(LPDIRECT3DDEVICE9 iface,
						    IDirect3DVertexDeclaration9** ppDecl)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%p)\n", iface, ppDecl);
  *ppDecl = This->state.vertex_decl;
  if (*ppDecl) IDirect3DVertexDeclaration9_AddRef(*ppDecl);
  return D3D_OK;
}

static HRESULT WINAPI Direct3DVertexShader9_QueryInterface(LPDIRECT3DVERTEXSHADER9 iface,
							   REFIID riid,
							   LPVOID* obj)
{
  ICOM_THIS(IDirect3DVertexShader9Impl, iface);
  TRACE("(%p)->(%s,%p)\n", iface, debugstr_guid(riid), obj);
  if (IsEqualGUID(&IID_IUnknown, riid) ||
      IsEqualGUID(&IID_IDirect3DVertexShader9, riid)) {
    This->ref++;
    *obj = This;
    return S_OK;
  }
  else {
    FIXME("(%p)->(%s): no interface", iface, debugstr_guid(riid));
    return E_NOINTERFACE;
  }
}

static ULONG WINAPI Direct3DVertexShader9_AddRef(LPDIRECT3DVERTEXSHADER9 iface)
{
  ICOM_THIS(IDirect3DVertexShader9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref;
}

static ULONG WINAPI Direct3DVertexShader9_Release(LPDIRECT3DVERTEXSHADER9 iface)
{
  ICOM_THIS(IDirect3DVertexShader9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_DELETEVERTEXSHADERFUNC, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;
  unsigned u;

  TRACE("(%p)->()\n", iface);
  if (--This->ref) return This->ref;
  for (u=0; u<This->cache_size; u++) {
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_DELETEVERTEXSHADER, 0, {1} };
    D3DHAL_DP2VERTEXSHADER data;
    IDirect3DVertexDeclaration9_Release((IDirect3DVertexDeclaration9*)This->cache[u].decl);
    data.dwHandle = VS_TO_HANDLE(This->cache[u].vs);
    This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
    HeapFree(GetProcessHeap(), 0, This->cache[u].vs);
  }
  if (dx9_hal) {
    data.dwHandle = (DWORD)This->vs;
    This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  }
  if (This->vs) {
    HeapFree(GetProcessHeap(), 0, This->vs);
  }
  HeapFree(GetProcessHeap(), 0, (void *)This->pFunction);
  HeapFree(GetProcessHeap(), 0, This);
  return 0;
}

static ICOM_VTABLE(IDirect3DVertexShader9) D3DVS9_Vtbl = {
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DVertexShader9_QueryInterface,
  Direct3DVertexShader9_AddRef,
  Direct3DVertexShader9_Release,
  (void*)0xdeada004, /* GetDevice */
  (void*)0xdeada005  /* GetFunction */
};

HRESULT WINAPI Direct3DDevice9_CreateVertexShader(LPDIRECT3DDEVICE9 iface,
						  const DWORD* pFunction,
						  IDirect3DVertexShader9** ppShader)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CREATEVERTEXSHADERFUNC, 0, {1} };
  D3DHAL_DP2CREATEVERTEXSHADERFUNC data;
  IDirect3DVertexShader9Impl* pShader;
  UINT len;

  TRACE("(%p)->(%p,%p)\n", iface, pFunction, ppShader);
  pShader = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DVertexShader9Impl));
  pShader->lpVtbl = &D3DVS9_Vtbl;
  pShader->parent = This;
  pShader->ref = 1;

  len = dump_vertex_program(pFunction);
  TRACE(" function length=%d dwords, %d bytes\n", len, len*sizeof(DWORD));
  pShader->FunctionLength = len*sizeof(DWORD);
  pShader->pFunction = HeapAlloc(buffer_heap, 0, pShader->FunctionLength);
  memcpy((void *)pShader->pFunction, (void *)pFunction, pShader->FunctionLength);

  TRACE(" => %p\n", pShader);

  *ppShader = (IDirect3DVertexShader9*)pShader;

  data.dwSize = pShader->FunctionLength;
  if (dx9_hal) {
    pShader->vs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(VertexShader));
    pShader->vs->pFunction = pShader->pFunction;
    pShader->vs->FunctionLength = pShader->FunctionLength;
    data.dwHandle = (DWORD)pShader->vs;
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data),
			  pShader->pFunction, pShader->FunctionLength);
  } else {
    return D3D_OK;
  }
}

HRESULT WINAPI Direct3DDevice9_SetVertexShader(LPDIRECT3DDEVICE9 iface,
					       IDirect3DVertexShader9* pShader)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADERFUNC, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;

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

  if (pShader == This->current->vertex_shader && This->current->dirty_vshader) return D3D_OK;
  if (pShader) IDirect3DVertexShader9_AddRef(pShader);
  if (This->current->vertex_shader) IDirect3DVertexShader9_Release(This->current->vertex_shader);
  This->current->vertex_shader = pShader;
  This->current->dirty_vshader = TRUE;
  if (!This->current->state_block)
    This->d3dp.vertex_shader = NULL; /* force reload */

  data.dwHandle = pShader ? (DWORD)((IDirect3DVertexShader9Impl*)pShader)->vs : 0;
  if (dx9_hal) {
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  } else {
    return D3D_OK;
  }
}

HRESULT WINAPI Direct3DDevice9_GetVertexShader(LPDIRECT3DDEVICE9 iface,
					       IDirect3DVertexShader9** ppShader)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%p)\n", iface, ppShader);
  *ppShader = This->state.vertex_shader;
  if (*ppShader) IDirect3DVertexShader9_AddRef(*ppShader);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetVertexShaderConstantF(LPDIRECT3DDEVICE9 iface,
							UINT StartRegister,
							CONST float* pConstantData,
							UINT Vector4fCount)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADERCONST, 0, {1} };
  D3DHAL_DP2SETVERTEXSHADERCONST data;

  TRACE("(%p)->(%d,%p,%d)\n", iface, StartRegister, pConstantData, Vector4fCount);

  if (!pConstantData) return D3DERR_INVALIDCALL;
  if (StartRegister + Vector4fCount > This->caps->MaxVertexShaderConst) return D3DERR_INVALIDCALL;

  /* FIXME do this in d3dgl instead? 
     memcpy(This->d3dp.vertex_const + 4*StartRegister, pConstantData, 4*sizeof(float)*Vector4fCount);
  */
  data.dwRegister = StartRegister;
  data.dwCount = Vector4fCount;
  if (dx8_hal)
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), 
			  (LPVOID)pConstantData, 4*sizeof(float)*Vector4fCount);
  else
    return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_GetVertexShaderConstantF(LPDIRECT3DDEVICE9 iface,
							UINT StartRegister,
							float* pConstantData,
							UINT Vector4fCount)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%d,%p,%d)\n", iface, StartRegister, pConstantData, Vector4fCount);

  if (StartRegister + Vector4fCount > This->caps->MaxVertexShaderConst) return D3DERR_INVALIDCALL;

  memcpy(pConstantData, This->d3dp.vertex_const + 4*StartRegister, 4*sizeof(float)*Vector4fCount);

  return D3D_OK;
}

#define FIXED_DECL(reg, usage, index) \
  D3DSIO_DCL, 0x80000000 | (D3DDECLUSAGE_##usage << D3DSP_DCL_USAGE_SHIFT) | (index << D3DSP_DCL_USAGEINDEX_SHIFT), reg | (D3DSPR_INPUT << D3DSP_REGTYPE_SHIFT)

static const DWORD fixed_func_decl[] = {
  D3DVS_VERSION(1,1),
  FIXED_DECL( 0, POSITION, 0),
  FIXED_DECL( 1, BLENDWEIGHT, 0),
  FIXED_DECL( 2, BLENDINDICES, 0),
  FIXED_DECL( 3, NORMAL, 0),
  FIXED_DECL( 4, PSIZE, 0),
  FIXED_DECL( 5, COLOR, 0),
  FIXED_DECL( 6, COLOR, 1),
  FIXED_DECL( 7, TEXCOORD, 0),
  FIXED_DECL( 8, TEXCOORD, 1),
  FIXED_DECL( 9, TEXCOORD, 2),
  FIXED_DECL(10, TEXCOORD, 3),
  FIXED_DECL(11, TEXCOORD, 4),
  FIXED_DECL(12, TEXCOORD, 5),
  FIXED_DECL(13, TEXCOORD, 6),
  FIXED_DECL(14, TEXCOORD, 7),
  FIXED_DECL(15, POSITION, 1),
  FIXED_DECL(16, NORMAL, 1),
  D3DVS_END()
};

#undef FIXED_DECL

HRESULT WINAPI Direct3DDevice9_BuildVertexShader(LPDIRECT3DDEVICE9 iface,
						 IDirect3DVertexDeclaration9Impl* pDecl,
						 IDirect3DVertexShader9Impl* pShader,
						 VertexShader** pvs)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  static const UINT type_len[256] = {1,2,3,4,1,1,1,2};
  CONST DWORD* program;
  CONST DWORD* ptr;
  CONST D3DVERTEXELEMENT9* vptr;
  DWORD* decl;
  DWORD* dptr;
  UINT dlen, ofs, tmplen;
  WORD last_stream;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CREATEVERTEXSHADER, 0, {1} };
  D3DHAL_DP2CREATEVERTEXSHADER data;
  VertexShader* vs;

  /* DirectX 8 driver compatibility */
  if (pShader) {
    unsigned u;

    if (dx9_hal) {
      *pvs = pShader->vs;
      return D3D_OK;
    }

    for (u=0; u<pShader->cache_size; u++) {
      if (pShader->cache[u].decl == pDecl) {
        *pvs = pShader->cache[u].vs;
        return D3D_OK;
      }
    }
    program = pShader->pFunction;
  }
  else {
    if (dx9_hal) {
      *pvs = NULL;
      return D3D_OK;
    }

    if (pDecl && pDecl->vs) {
      *pvs = pDecl->vs;
      return D3D_OK;
    }
    program = fixed_func_decl;
  }

  /* generate a DX8 declaration that combines pDecl with the program's DCLs and DEFs. */

  dump_vertex_program(program);

  dlen = pDecl->Streams + pDecl->DeclarationLength*2;
  /* count DEFs */
  ptr = program+1;
  do {
    if (ptr[0] == D3DSIO_DEF)
      dlen += 5;
    tmplen = hdr_op_len(ptr);
    ptr += tmplen;
  } while (tmplen);
  /* allocate DX8 declaration */
  decl = HeapAlloc(GetProcessHeap(), 0, dlen*sizeof(DWORD));
  dptr = decl;
  /* convert DEFs */
  ptr = program+1;
  do {
    if (ptr[0] == D3DSIO_DEF) {
      TRACE(" def c[%ld]\n", ptr[1] & D3DSP_REGNUM_MASK);
      *dptr++ = D3DVSD_CONST(ptr[1] & D3DSP_REGNUM_MASK, 1);
      *dptr++ = ptr[2];
      *dptr++ = ptr[3];
      *dptr++ = ptr[4];
      *dptr++ = ptr[5];
    }
    tmplen = hdr_op_len(ptr);
    ptr += tmplen;
  } while (tmplen);
  /* match pDecl with DCLs */
  last_stream = 0xff;
  ofs = 0;
  for (vptr = pDecl->pDeclaration; vptr->Stream != 0xFF; vptr++) {
    DWORD match = 0x80000000 | (DWORD)(vptr->Usage << D3DSP_DCL_USAGE_SHIFT) | (DWORD)(vptr->UsageIndex << D3DSP_DCL_USAGEINDEX_SHIFT);
    if (vptr->Stream != last_stream) {
      *dptr++ = D3DVSD_STREAM(vptr->Stream);
      last_stream = vptr->Stream;
      TRACE(" stream %d\n", last_stream);
      ofs = 0;
    }
    if (vptr->Offset > ofs) {
      tmplen = (vptr->Offset - ofs) / sizeof(DWORD);
      *dptr++ = D3DVSD_SKIP(tmplen);
      ofs += tmplen * sizeof(DWORD);
    }
    if (vptr->Offset != ofs) {
      FIXME("unexpected offset in vertex declaration (%d != %d)\n", vptr->Offset, ofs);
    }
    /* look for match */
    ptr = program+1;
    do {
      if ((ptr[0] == D3DSIO_DCL) && (ptr[1] == match))
        break;
      tmplen = hdr_op_len(ptr);
      ptr += tmplen;
    } while (tmplen);
    if (ptr[0] == D3DSIO_DCL) {
      TRACE("  matched usage %d index %d type %d with register v%ld\n", vptr->Usage, vptr->UsageIndex, vptr->Type, ptr[2] & D3DSP_REGNUM_MASK);
      *dptr++ = D3DVSD_REG(ptr[2] & D3DSP_REGNUM_MASK, vptr->Type);
    } else {
      TRACE("  matched usage %d index %d type %d with nothing\n", vptr->Usage, vptr->UsageIndex, vptr->Type);
      if (type_len[vptr->Type])
        *dptr++ = D3DVSD_SKIP(type_len[vptr->Type]);
    }
    ofs += type_len[vptr->Type] * sizeof(DWORD);
  }
  *dptr = D3DVSD_END();
  if (dptr - decl >= dlen) {
    FIXME("buffer overflow when converting vertex declaration\n");
  }

  /* finally, create DX8 vertex program */

  vs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(VertexShader));
  vs->DeclarationLength = dlen;
  vs->pDeclaration = decl;
  vs->FVF = pDecl->FVF;
  if (pShader) {
    vs->FunctionLength = pShader->FunctionLength;
    vs->pFunction = pShader->pFunction;
    pShader->cache = HeapReAlloc(GetProcessHeap(), 0, pShader->cache, (pShader->cache_size+1)*sizeof(struct vs_cache));
    pShader->cache[pShader->cache_size].decl = pDecl;
    if (pDecl) IDirect3DVertexDeclaration9_AddRef((IDirect3DVertexDeclaration9*)pDecl);
    pShader->cache[pShader->cache_size].vs = vs;
    pShader->cache_size++;
  } else {
    if (pDecl) pDecl->vs = vs;
  }

  *pvs = vs;

  data.dwHandle = VS_TO_HANDLE(vs);
  data.dwDeclSize = vs->DeclarationLength;
  data.dwCodeSize = vs->FunctionLength;
  if (dx8_hal)
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  else
    return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_GetStridedData(LPDIRECT3DDEVICE9 iface,
					      LPD3DDRAWPRIMITIVESTRIDEDDATA lpData)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  HRESULT hr;
  hr = Direct3DDevice9_GetStridedDataUP(iface, lpData,
					(LPVOID)This->d3dp.stream[0].data_t->gbl.fpVidMem,
					This->d3dp.stream[0].stride, TRUE);
  /* FIXME: cache returned strided data */
  return hr;
}

HRESULT WINAPI Direct3DDevice9_GetStridedDataUP(LPDIRECT3DDEVICE9 iface,
						LPD3DDRAWPRIMITIVESTRIDEDDATA lpData,
						CONST void* vertex_data,
						UINT vertex_stride,
						BOOL verify_stride)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  memset(lpData, 0, sizeof(*lpData));
  lpData->position.lpvData = (LPVOID)vertex_data;
  lpData->position.dwStride = vertex_stride;
  if (!This->d3dp.vertex_shader) {
    /* FVF fixed-function vertex shader */
    static const DWORD position_sizes[] = { 0, 3, 4, 4, 5, 6, 7, 8 };
    LPBYTE ptr = (LPBYTE)vertex_data;
    UINT stride;
    int tex_count = D3DFVF_TEXCOUNT(This->d3dp.vertex_fvf);
    int i;

    stride = position_sizes[(This->d3dp.vertex_fvf & D3DFVF_POSITION_MASK) / 2]
      * sizeof(float);
    if (This->d3dp.vertex_fvf & D3DFVF_NORMAL) {
      lpData->normal.lpvData = ptr + stride;
      lpData->normal.dwStride = vertex_stride;
      stride += 3*sizeof(float);
    }
    if (This->d3dp.vertex_fvf & D3DFVF_PSIZE) {
      /* FIXME: point size */
      stride += sizeof(float);
    }
    if (This->d3dp.vertex_fvf & D3DFVF_DIFFUSE) {
      lpData->diffuse.lpvData = ptr + stride;
      lpData->diffuse.dwStride = vertex_stride;
      stride += sizeof(D3DCOLOR);
    }
    if (This->d3dp.vertex_fvf & D3DFVF_SPECULAR) {
      lpData->specular.lpvData = ptr + stride;
      lpData->specular.dwStride = vertex_stride;
      stride += sizeof(D3DCOLOR);
    }
    if (This->d3dp.vertex_fvf & D3DFVF_TEXCOUNT_MASK) {
      int num_coords;
      for (i = 0; i < tex_count; i++) {
	num_coords = D3DFVF_TEXCOORDSIZE(This->d3dp.vertex_fvf,i);
	lpData->textureCoords[i].lpvData = ptr + stride;
	lpData->textureCoords[i].dwStride = vertex_stride;
	stride += num_coords * sizeof(float);
      }
      /* initialize unused pointers/strides */
      for (i = tex_count; i < D3DDP_MAXTEXCOORD; i++ ) {
	lpData->textureCoords[i].lpvData = NULL;
	lpData->textureCoords[i].dwStride = 0;
      }
    }
    if ((verify_stride && stride != vertex_stride) ||
        (stride > vertex_stride)) {
      ERR("stride inconsistency! %d != %d\n", stride, vertex_stride);
    }
  }
  else if (!This->d3dp.vertex_shader->pFunction) {
    /* fixed function vertex shaders are handled in fixedfunc.c */
  }
  else {
    /* programmable function vertex shaders are handled in vprogram.c */
  }
  return D3D_OK;
}

/******************************************************************
 *              Direct3DShaderValidatorCreate9 (D3D9.@)
 */
LPVOID WINAPI Direct3DShaderValidatorCreate9(void)
{
  /* this function is undocumented */
  TRACE("()\n");
  return NULL; /* no validators from us. */
}
