/*
 * Direct3D8 vertex shaders
 * Copyright (c) 2002, TransGaming Technologies Inc.
 */

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

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

#include "d3d8_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 )

static UINT dump_vs_declaration(CONST DWORD* pDecl)
{
  static const char* const data_types[] = {
    "D3DVSDT_FLOAT1",
    "D3DVSDT_FLOAT2",
    "D3DVSDT_FLOAT3",
    "D3DVSDT_FLOAT4",
    "D3DVSDT_D3DCOLOR",
    "D3DVSDT_UBYTE4",
    "D3DVSDT_SHORT2",
    "D3DVSDT_SHORT4",
    "8","9","10","11","12","13","14","15"
  };
  CONST DWORD tok = *pDecl;
  UINT len = 1;  /* most tokens have a length of 1 DWORD */
  
  switch ((tok & D3DVSD_TOKENTYPEMASK) >> D3DVSD_TOKENTYPESHIFT) {
  case D3DVSD_TOKEN_NOP:  /* NOP Token */
    /* NOP must be all zeros */
    if( tok == D3DVSD_NOP() ) {
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx NOP()\n", tok);
    } else {
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid NOP token\n", tok);
    }
    break;
  case D3DVSD_TOKEN_STREAM: /* Stream Selector */
    if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_STREAMTESSMASK|D3DVSD_STREAMNUMBERMASK)) != 0) {
      /* unused bits are non-zero */
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid STREAM token\n", tok);
    } else if (tok & D3DVSD_STREAMTESSMASK) {
      /* Stream Selector: tesselator stream */
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx STREAM Selector: STREAM(TESS)\n", tok);
    } else {
      /* Stream Selector: stream # */
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx STREAM Selector: STREAM(%ld)\n", tok,
              ((tok & D3DVSD_STREAMNUMBERMASK) >> D3DVSD_STREAMNUMBERSHIFT));
    }
    break;
  case D3DVSD_TOKEN_STREAMDATA: /* Stream Data Definition */
    if (tok & D3DVSD_DATALOADTYPEMASK) {
      if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_DATALOADTYPEMASK|D3DVSD_SKIPCOUNTMASK)) != 0) {
        /* unused bits are non-zero */
        if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid STREAM Data Def (SKIP) token\n", tok);
      } else {
        /* Data Skip: # of dwords to skip */
        if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx STREAM Data Def: SKIP(%ld)\n", tok,
                ((tok & D3DVSD_SKIPCOUNTMASK) >> D3DVSD_SKIPCOUNTSHIFT));
      }
    }
    else {
      if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_DATATYPEMASK|D3DVSD_VERTEXREGMASK)) != 0) {
        /* unused bits are non-zero */
        if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid STREAM Data Def (REG)\n", tok);
      } else {
        /* Vertex Input Register Load: data type/dimension, vertex register # */
        if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx STREAM Data Def: REG(%ld,%s)\n", tok,
                ((tok & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT),
                data_types[(tok & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT]);
      }
    }
    break;
  case D3DVSD_TOKEN_TESSELLATOR: /* Stream Data Definition (Tesselator data) */
    if (tok & D3DVSD_DATALOADTYPEMASK) {
      if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_DATALOADTYPEMASK|
                   D3DVSD_DATATYPEMASK|D3DVSD_VERTEXREGMASK)) != 0 ) {
        if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid STREAM Data Def (TESSUV) token\n", tok);
      } else {
        /* Tesselator UV data: data type/dimension, Vertex reg out */
        if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx STREAM Data Def: TESSUV(%s,%ld)\n", tok,
		data_types[(tok & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT],
		((tok & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT));
      }
    } else {
      if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_VERTEXREGINMASK|
		   D3DVSD_DATATYPEMASK|D3DVSD_VERTEXREGMASK)) != 0 ) {
	if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid STREAM Data Def (TESSNORMAL) token\n", tok);
      } else {
        /* Tesselator normal data: Vertex reg in, data type/dimension, Vertex reg out */
	if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx STREAM Data Def: TESSNORMAL(%ld,%ld,%s)\n", tok,
		((tok & D3DVSD_VERTEXREGINMASK) >> D3DVSD_VERTEXREGINSHIFT),
                ((tok & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT),
                data_types[(tok & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT]);
      }
    }
    break;
  case D3DVSD_TOKEN_CONSTMEM:  /* Constant Memory from Shader (multiple DWORD token) */
    if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_CONSTCOUNTMASK|D3DVSD_CONSTADDRESSMASK)) != 0 ) {
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid CONSTMEM token\n", tok);
    } else {
      DWORD i;
      DWORD count = (tok & D3DVSD_CONSTCOUNTMASK) >> D3DVSD_CONSTCOUNTSHIFT;
      DWORD addr  = (tok & D3DVSD_CONSTADDRESSMASK) >> D3DVSD_CONSTADDRESSSHIFT;
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx CONSTMEM(%ld,%ld)\n", tok, count, addr);
      pDecl++;
      for( i=0; i<count; i++ ) {
	if (TRACE_ON(ddraw)) DPRINTF(" declaration:     c[%ld] = (%08lx,%08lx,%08lx,%08lx)\n", 
		addr, *pDecl, *(pDecl+1), *(pDecl+2), *(pDecl+3));
	pDecl += 4; 
	addr++;
      }
      len += 4*count;
    }
    break;
  case D3DVSD_TOKEN_EXT:
    {
      DWORD i;
      DWORD count = (tok & D3DVSD_EXTCOUNTMASK) >> D3DVSD_EXTCOUNTSHIFT;
      DWORD info = (tok & D3DVSD_EXTINFOMASK) >> D3DVSD_EXTINFOSHIFT;
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx EXTENSION(%ld,%ld)\n", tok, count, info);
      for( i=0; i<count; i++ ) {
	pDecl++;
	if (TRACE_ON(ddraw)) DPRINTF(" declaration:     %08lx\n", *pDecl);
      }
      len += count;
    }
    break;
  case D3DVSD_TOKEN_END:
    if( tok == D3DVSD_END() ) {
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx END()\n", tok);
    } else {
      if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx invalid END token\n", tok);
    }
    break;
  default:
    if (TRACE_ON(ddraw)) DPRINTF(" declaration: %08lx (unknown)\n", tok);
    break;
  }
  return len;
}

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 >> D3DSP_REGTYPE_SHIFT:
	if (TRACE_ON(ddraw)) DPRINTF("r%lu", regnum);
	break;

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

    case D3DSPR_CONST >> D3DSP_REGTYPE_SHIFT:
	/* 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 >> D3DSP_REGTYPE_SHIFT:
	if (TRACE_ON(ddraw)) DPRINTF("a%lu", regnum);
	break;

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

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

    case D3DSPR_TEXCRDOUT >> D3DSP_REGTYPE_SHIFT:
	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 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 {
	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);
}

#if 0
/************************************************************************/
/* vertex program validation */

#define VS_REGISTER_MASK (D3DSP_REGNUM_MASK | D3DSP_REGTYPE_MASK)
#define VS_REGISTER_A0 (D3DSPR_ADDR | 0)

#define VS_OPERAND_BIT 0x80000000

/* Unspecified result for the version tag,
 * Unspecified result for comment contents (comment tag is an opcode)
 * End tag is an opcode.
 * Otherwise these do the obvious thing.
 */

static inline int is_operand_tag(DWORD tag) { return tag & VS_OPERAND_BIT; }
static inline int is_opcode_tag(DWORD tag) { return !is_operand_tag(tag); }

static inline int is_comment_tag(DWORD tag)
{
    return is_opcode_tag(tag)
	&& ((tag & D3DSI_OPCODE_MASK) == D3DSIO_COMMENT);
}

static BOOL validate_destination_operand(DWORD operand)
{
    /* PS: different */
    static const BYTE regmax[8] = { 12, 0, 0, 1, 3, 2, 4 , 0 };

    DWORD regnum = operand & D3DSP_REGNUM_MASK;
    DWORD regtype = (operand & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT;

    if (!is_operand_tag(operand)) return FALSE;

    /* verify that reserved bits are 0.
     * PS: some of these are not reserved. (0x0000E000) */
    if (operand & 0x0FF0E000) return FALSE;

    if (regnum >= regmax[regtype]) return FALSE;

    /* you must write to something (unofficial) */
    if ((operand & D3DSP_WRITEMASK_ALL) == 0) return FALSE;

    /* only a0.x can be written */
    if (regtype == (D3DSPR_ADDR >> D3DSP_REGTYPE_SHIFT)
	&& (operand & D3DSP_WRITEMASK_ALL) != D3DSP_WRITEMASK_0)
	return FALSE;

    /* PS: also validate result modifier and result shift scale */

    return TRUE;
}

static BOOL validate_source_operand(DWORD operand)
{
    /* PS: different */
    static const BYTE regmax[8] = { 12, 16, 96, 0, 0, 0, 0, 0 };

    DWORD regnum = operand & D3DSP_REGNUM_MASK;
    DWORD regtype = (operand & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT;
    DWORD srcmod = operand & D3DSP_SRCMOD_MASK;

    if (!is_operand_tag(operand)) return FALSE;

    /* PS: verify that reserved bits are 0. (0x0000E000) */

    if (regnum >= regmax[regtype]) return FALSE;

    /* PS: does not apply */
    if (operand & D3DVS_ADDRMODE_RELATIVE)
    {
	if (regtype != D3DSPR_CONST >> D3DSP_REGTYPE_SHIFT) return FALSE;

	/* XXX validate the -64 to +63 range, if appropriate */
    }

    if (srcmod != D3DSPSM_NONE && srcmod != D3DSPSM_NEG) return FALSE;

    return TRUE;
}

static BOOL validate_instruction(const DWORD* program)
{
    DWORD opcode = *program++;
    DWORD num_operands;
    DWORD i = 1;

    if (!is_opcode_tag(opcode)) return FALSE;
    /* PS: allow the coissue bit */
    if (opcode & ~D3DSI_OPCODE_MASK) return FALSE;
    if (!is_recognised_opcode(opcode)) return FALSE;

    opcode &= D3DSI_OPCODE_MASK;

    num_operands = num_opcode_operands(opcode);

    /* If there is at least one operand, then there is a destination
     * operand, and we require (though I have never seen explicit
     * language in the docs) that it be the first operand. */
    if (num_operands > 0)
    {
	if (!validate_destination_operand(program[i])) return FALSE;

	/* if we're writing to a0.x, verify that the opcode is MOV */
	if ((program[i] & VS_REGISTER_MASK) == VS_REGISTER_A0
	    && opcode != D3DSIO_MOV)
	    return FALSE;

	i++;
    }

    for (i = 1; i < num_operands; i++)
    {
	if (!validate_source_operand(program[i])) return FALSE;
    }

    /* no extra operands */
    if (!is_opcode_tag(program[num_operands])) return FALSE;

    /* check that we don't have any illegal src/dst combinations:
     * only one constant or input register can be sourced */
    if (num_operands > 1)
    {
	BOOL constant_used = FALSE;
	BOOL input_used = FALSE;
	DWORD constant_reg, input_reg;

	for (i = 1; i < num_operands; i++)
	{
	    DWORD regnum = program[i] & D3DSP_REGNUM_MASK;

	    if ((program[i] & D3DSP_REGTYPE_MASK) == D3DSPR_INPUT)
	    {
		if (input_used && input_reg != regnum)
		    return FALSE;

		input_used = TRUE;
		input_reg = regnum;
	    }
	    else if ((program[i] & D3DSP_REGTYPE_MASK) == D3DSPR_CONST)
	    {
		if (constant_used && constant_reg != regnum)
		    return FALSE;

		constant_used = TRUE;
		constant_reg = regnum;
	    }
	}
    }

    return TRUE;
}

static inline const DWORD* next_instruction(const DWORD* program)
{
    while (is_operand_tag(*++program)) /* nothing */ ;

    return program;
}

static BOOL validate_comment(const DWORD* program)
{
    DWORD comment_len;

    if (!is_comment_tag(*program)) return FALSE;

    comment_len = (*program & D3DSI_COMMENTSIZE_MASK)
	>> D3DSI_COMMENTSIZE_SHIFT;

    /* XXX: Just what is a valid comment? Given that we have a
     * length, does it have to be NUL-terminated?
     * We might as well just consider it to be arbitrary data.
     * Are 0-length comments allowed?
     */

    return TRUE;
}

const DWORD* skip_comment(const DWORD* program)
{
    DWORD comment_len;

    comment_len = (*program & D3DSI_COMMENTSIZE_MASK)
	>> D3DSI_COMMENTSIZE_SHIFT;

    return program + comment_len + 1;
}

static BOOL validate_program_version_tag(DWORD version)
{
    if (HIWORD(version) != 0xFFFE) return FALSE;

    /* 1.0, 1.1 are supported. */
    if (D3DSHADER_VERSION_MAJOR(version) != 1
	|| D3DSHADER_VERSION_MINOR(version) > 1)
	return FALSE;

    return TRUE;
}

static BOOL validate_vertex_program(const DWORD* program,
				    LPDWORD num_insns)
{
    if (!validate_program_version_tag(*program++)) return FALSE;

    while (*program != D3DVS_END())
    {
	if (is_comment_tag(*program))
	{
	    if (!validate_comment(program)) return FALSE;
	    program = skip_comment(program);
	    if (!is_opcode_tag(*program)) return FALSE;
	}
	else
	{
	    if (!validate_instruction(program)) return FALSE;
	    program = next_instruction(program);
	}
    }

    /* XXX Must DX8 vertex programs write oPos? */
    /* XXX Supposedly temporary registers must be written before they
     * can be read. What does that mean with respect to components?
     * Is a program that only writes to v0.x, but then only uses that
     * component legitimate?
     */

    /* And how should we check resource requirements.
     * We need to count # instructions (after expanding macros),
     * possibly count the highest register number from each class.
     */

    return TRUE;
}
#endif

/***************************************************************************
 * Implementation of D3D VertexShader API funtions
 ***************************************************************************/

HRESULT WINAPI Direct3DDevice8_CreateVertexShader(LPDIRECT3DDEVICE8 iface,
						  CONST DWORD* pDeclaration,
						  CONST DWORD* pFunction,
						  DWORD* pHandle,
						  DWORD Usage)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CREATEVERTEXSHADER, 0, {1} };
  D3DHAL_DP2CREATEVERTEXSHADER data;
  VertexShader* vs;

  TRACE("(%p)->(%p,%p,%p,%ld)\n", iface, pDeclaration, pFunction, pHandle, Usage);

  vs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(VertexShader));
  vs->Usage = Usage;

  if (pDeclaration) {
    CONST DWORD* ptr = pDeclaration;
    UINT len = 0;
    DWORD fvf = 0, pos = 0, tex = 0;

    while (*ptr != D3DVSD_END()) {
      UINT toklen;
      //if (TRACE_ON(ddraw))
      toklen = dump_vs_declaration(ptr);
      
      if (!pFunction) {
        /* if this is a vertex shader for the fixed-function pipeline,
         * life becomes easier when it has a FVF to refer to */
        if ((((*ptr & D3DVSD_TOKENTYPEMASK) >> D3DVSD_TOKENTYPESHIFT) == D3DVSD_TOKEN_STREAMDATA) &&
            !(*ptr & D3DVSD_DATALOADTYPEMASK)) {
          DWORD reg = (*ptr & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT;
          DWORD type = (*ptr & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT;
          switch (reg) {
          case D3DVSDE_POSITION: pos = max(pos, 1); break;
          case D3DVSDE_BLENDWEIGHT:
            switch (type) {
            case D3DVSDT_FLOAT1: pos = max(pos, 3); break;
            case D3DVSDT_FLOAT2: pos = max(pos, 4); break;
            case D3DVSDT_FLOAT3: pos = max(pos, 5); break;
            case D3DVSDT_FLOAT4: pos = max(pos, 6); break;
            default: /* ? */ break;
            }
            break;
          case D3DVSDE_BLENDINDICES: fvf |= D3DFVF_LASTBETA_UBYTE4; break;
          case D3DVSDE_NORMAL:   fvf |= D3DFVF_NORMAL; break;
          case D3DVSDE_PSIZE:    fvf |= D3DFVF_PSIZE; break;
          case D3DVSDE_DIFFUSE:  fvf |= D3DFVF_DIFFUSE; break;
          case D3DVSDE_SPECULAR: fvf |= D3DFVF_SPECULAR; break;
          case D3DVSDE_TEXCOORD0: tex = max(tex, 1); break;
          case D3DVSDE_TEXCOORD1: tex = max(tex, 2); break;
          case D3DVSDE_TEXCOORD2: tex = max(tex, 3); break;
          case D3DVSDE_TEXCOORD3: tex = max(tex, 4); break;
          case D3DVSDE_TEXCOORD4: tex = max(tex, 5); break;
          case D3DVSDE_TEXCOORD5: tex = max(tex, 6); break;
          case D3DVSDE_TEXCOORD6: tex = max(tex, 7); break;
          case D3DVSDE_TEXCOORD7: tex = max(tex, 8); break;
          case D3DVSDE_POSITION2: break; /* FIXME: for tweening */
          case D3DVSDE_NORMAL2:   break; /* FIXME: for tweening */
          }
        }
        if ((pos > 2) && (fvf & D3DFVF_LASTBETA_UBYTE4)) pos++;
        fvf |= (pos << 1) | (tex << D3DFVF_TEXCOUNT_SHIFT);
        vs->FVF = fvf;
      }
      ptr+=toklen; len+=toklen;
    }
    len++; // count the END() token
    TRACE(" declaration length=%d dwords, %d bytes\n", len, len*sizeof(DWORD));
    vs->DeclarationLength = len*sizeof(DWORD);
    vs->pDeclaration = HeapAlloc(buffer_heap, 0, vs->DeclarationLength);
    memcpy((void *)vs->pDeclaration, (void *)pDeclaration, vs->DeclarationLength);
  }

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

  /* add vertex shader to linked list */
  if (!This->vshaders) {
    This->vshaders = vs;
  } else {
    This->vshaders->prev = vs;
    vs->next = This->vshaders;
    This->vshaders = vs;
  }
  
  *pHandle = VS_TO_HANDLE(vs);

  data.dwHandle = *pHandle;
  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 Direct3DDevice8_SetVertexShader(LPDIRECT3DDEVICE8 iface,
					       DWORD Handle)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADER, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;

  TRACE("(%p)->(0x%lx)\n", iface, Handle);
  if (IS_VS(Handle)) {
    This->d3dp.vertex_shader = VS_FROM_HANDLE(Handle);
    This->d3dp.vertex_fvf = This->d3dp.vertex_shader->FVF;
  }
  else {
    This->d3dp.vertex_shader = NULL;
    This->d3dp.vertex_fvf = Handle;
  }

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

HRESULT WINAPI Direct3DDevice8_GetVertexShader(LPDIRECT3DDEVICE8 iface,
					       DWORD* pHandle)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%p)\n", iface, pHandle);
  if (This->d3dp.vertex_shader)
    *pHandle = VS_TO_HANDLE(This->d3dp.vertex_shader);
  else
    *pHandle = This->d3dp.vertex_fvf;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_DeleteVertexShader(LPDIRECT3DDEVICE8 iface,
						  DWORD Handle)
{
  ICOM_THIS(IDirect3DDevice8Impl, 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;
}

HRESULT WINAPI Direct3DDevice8_SetVertexShaderConstant(LPDIRECT3DDEVICE8 iface,
						       DWORD Register,
						       CONST void* pConstantData,
						       DWORD ConstantCount)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADERCONST, 0, {1} };
  D3DHAL_DP2SETVERTEXSHADERCONST data;

  TRACE("(%p)->(%ld,%p,%ld)\n", iface, Register, pConstantData, ConstantCount);

  if (!pConstantData) return D3DERR_INVALIDCALL;
  if (Register + ConstantCount > This->caps->MaxVertexShaderConst) return D3DERR_INVALIDCALL;

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

HRESULT WINAPI Direct3DDevice8_GetVertexShaderConstant(LPDIRECT3DDEVICE8 iface,
						       DWORD Register,
						       void* pConstantData,
						       DWORD ConstantCount)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%ld,%p,%ld)\n", iface, Register, pConstantData, ConstantCount);

  if (Register + ConstantCount > This->caps->MaxVertexShaderConst) return D3DERR_INVALIDCALL;

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

  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 Direct3DDevice8_GetVertexShaderDeclaration(LPDIRECT3DDEVICE8 iface,
							  DWORD Handle,
							  void* pData,
							  DWORD* pSizeOfData)
{
  VertexShader *vs;
  /* ICOM_THIS(IDirect3DDevice8Impl, 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 Direct3DDevice8_GetVertexShaderFunction(LPDIRECT3DDEVICE8 iface,
						       DWORD Handle,
						       void* pData,
						       DWORD* pSizeOfData)
{
  VertexShader *vs;
  /* ICOM_THIS(IDirect3DDevice8Impl, 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;
}

HRESULT WINAPI Direct3DDevice8_GetStridedData(LPDIRECT3DDEVICE8 iface,
					      LPD3DDRAWPRIMITIVESTRIDEDDATA lpData)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  HRESULT hr;
  hr = Direct3DDevice8_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 Direct3DDevice8_GetStridedDataUP(LPDIRECT3DDEVICE8 iface,
						LPD3DDRAWPRIMITIVESTRIDEDDATA lpData,
						CONST void* vertex_data,
						UINT vertex_stride,
						BOOL verify_stride)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  memset(lpData, 0, sizeof(*lpData));
  lpData->position.lpvData = 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;
}

/******************************************************************
 *              ValidateVertexShader (D3D8.@)
 * Undocumented function that seems to be called by D3DXAssembleShader
 * to verify the syntax of the vertex shader.  Seems to be related to 
 * the Flags (D3DXASM) parameter of D3DXAssembleShader.
 * 
 * We can probably get away without implementing this function, because
 * this is more of a debug feature.
 * 
 */
BOOL WINAPI ValidateVertexShader(LPVOID stuff)
{
  /* this function is undocumented */
  TRACE("(%p)\n", stuff);
  return TRUE;
}
