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

#include <assert.h>
#include <stdio.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 */

/* One of the vertex shader input register types is D3DVSDT_D3DCOLOR
 * which is in BGRA ordering.  We must convert this to RGBA ordering
 * since this is what openGL uses.  It seems the best way to do it is
 * to apply the appropriate swizzle to the read from the input color 
 * register. However, since I also wrote code to swizzle on the write 
 * to the output color register, I am going to leave it in for now 
 * (it can be enabled changing the following define to OUTPUT_D3DCOLOR_FIX) */
#define INPUTREG_D3DCOLOR_FIX

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

/********* NV_vertex_program defines from GL specs *************/
#ifndef GL_VERTEX_PROGRAM_NV
#define GL_VERTEX_PROGRAM_NV              0x8620
#define GL_VERTEX_STATE_PROGRAM_NV        0x8621
#define GL_ATTRIB_ARRAY_SIZE_NV           0x8623
#define GL_ATTRIB_ARRAY_STRIDE_NV         0x8624
#define GL_ATTRIB_ARRAY_TYPE_NV           0x8625
#define GL_CURRENT_ATTRIB_NV              0x8626
#define GL_PROGRAM_LENGTH_NV              0x8627
#define GL_PROGRAM_STRING_NV              0x8628
#define GL_MODELVIEW_PROJECTION_NV        0x8629
#define GL_IDENTITY_NV                    0x862A
#define GL_INVERSE_NV                     0x862B
#define GL_TRANSPOSE_NV                   0x862C
#define GL_INVERSE_TRANSPOSE_NV           0x862D
#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E
#define GL_MAX_TRACK_MATRICES_NV          0x862F
#define GL_MATRIX0_NV                     0x8630
#define GL_MATRIX1_NV                     0x8631
#define GL_MATRIX2_NV                     0x8632
#define GL_MATRIX3_NV                     0x8633
#define GL_MATRIX4_NV                     0x8634
#define GL_MATRIX5_NV                     0x8635
#define GL_MATRIX6_NV                     0x8636
#define GL_MATRIX7_NV                     0x8637
#define GL_CURRENT_MATRIX_STACK_DEPTH_NV  0x8640
#define GL_CURRENT_MATRIX_NV              0x8641
#define GL_VERTEX_PROGRAM_POINT_SIZE_NV   0x8642
#define GL_VERTEX_PROGRAM_TWO_SIDE_NV     0x8643
#define GL_PROGRAM_PARAMETER_NV           0x8644
#define GL_ATTRIB_ARRAY_POINTER_NV        0x8645
#define GL_PROGRAM_TARGET_NV              0x8646
#define GL_PROGRAM_RESIDENT_NV            0x8647
#define GL_TRACK_MATRIX_NV                0x8648
#define GL_TRACK_MATRIX_TRANSFORM_NV      0x8649
#define GL_VERTEX_PROGRAM_BINDING_NV      0x864A
#define GL_PROGRAM_ERROR_POSITION_NV      0x864B
#define GL_VERTEX_ATTRIB_ARRAY0_NV        0x8650
#define GL_VERTEX_ATTRIB_ARRAY1_NV        0x8651
#define GL_VERTEX_ATTRIB_ARRAY2_NV        0x8652
#define GL_VERTEX_ATTRIB_ARRAY3_NV        0x8653
#define GL_VERTEX_ATTRIB_ARRAY4_NV        0x8654
#define GL_VERTEX_ATTRIB_ARRAY5_NV        0x8655
#define GL_VERTEX_ATTRIB_ARRAY6_NV        0x8656
#define GL_VERTEX_ATTRIB_ARRAY7_NV        0x8657
#define GL_VERTEX_ATTRIB_ARRAY8_NV        0x8658
#define GL_VERTEX_ATTRIB_ARRAY9_NV        0x8659
#define GL_VERTEX_ATTRIB_ARRAY10_NV       0x865A
#define GL_VERTEX_ATTRIB_ARRAY11_NV       0x865B
#define GL_VERTEX_ATTRIB_ARRAY12_NV       0x865C
#define GL_VERTEX_ATTRIB_ARRAY13_NV       0x865D
#define GL_VERTEX_ATTRIB_ARRAY14_NV       0x865E
#define GL_VERTEX_ATTRIB_ARRAY15_NV       0x865F
#endif
/******* end NV_vertex_program defines *************************/

/*******    ARB_vertex_program defines *************************/
#ifndef GL_ARB_vertex_program
#define GL_ARB_vertex_program 1
#define GL_VERTEX_PROGRAM_ARB             0x8620
#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB  0x8642
#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB    0x8643
#define GL_COLOR_SUM_ARB                  0x8458
#define GL_PROGRAM_FORMAT_ASCII_ARB       0x8875
#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622
#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB   0x8623
#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624
#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB   0x8625
#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A
#define GL_CURRENT_VERTEX_ATTRIB_ARB      0x8626
#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645
#define GL_PROGRAM_LENGTH_ARB             0x8627
#define GL_PROGRAM_FORMAT_ARB             0x8876
#define GL_PROGRAM_BINDING_ARB            0x8677
#define GL_PROGRAM_INSTRUCTIONS_ARB       0x88A0
#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB   0x88A1
#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2
#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3
#define GL_PROGRAM_TEMPORARIES_ARB        0x88A4
#define GL_MAX_PROGRAM_TEMPORARIES_ARB    0x88A5
#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6
#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7
#define GL_PROGRAM_PARAMETERS_ARB         0x88A8
#define GL_MAX_PROGRAM_PARAMETERS_ARB     0x88A9
#define GL_PROGRAM_NATIVE_PARAMETERS_ARB  0x88AA
#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB
#define GL_PROGRAM_ATTRIBS_ARB            0x88AC
#define GL_MAX_PROGRAM_ATTRIBS_ARB        0x88AD
#define GL_PROGRAM_NATIVE_ATTRIBS_ARB     0x88AE
#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF
#define GL_PROGRAM_ADDRESS_REGISTERS_ARB  0x88B0
#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1
#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2
#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3
#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4
#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5
#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6
#define GL_PROGRAM_STRING_ARB             0x8628
#define GL_PROGRAM_ERROR_POSITION_ARB     0x864B
#define GL_CURRENT_MATRIX_ARB             0x8641
#define GL_TRANSPOSE_CURRENT_MATRIX_ARB   0x88B7
#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640
#define GL_MAX_VERTEX_ATTRIBS_ARB         0x8869
#define GL_MAX_PROGRAM_MATRICES_ARB       0x862F
#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E
#define GL_PROGRAM_ERROR_STRING_ARB       0x8874
#define GL_MATRIX0_ARB                    0x88C0
#define GL_MATRIX1_ARB                    0x88C1
#define GL_MATRIX2_ARB                    0x88C2
#define GL_MATRIX3_ARB                    0x88C3
#define GL_MATRIX4_ARB                    0x88C4
#define GL_MATRIX5_ARB                    0x88C5
#define GL_MATRIX6_ARB                    0x88C6
#define GL_MATRIX7_ARB                    0x88C7
#define GL_MATRIX8_ARB                    0x88C8
#define GL_MATRIX9_ARB                    0x88C9
#define GL_MATRIX10_ARB                   0x88CA
#define GL_MATRIX11_ARB                   0x88CB
#define GL_MATRIX12_ARB                   0x88CC
#define GL_MATRIX13_ARB                   0x88CD
#define GL_MATRIX14_ARB                   0x88CE
#define GL_MATRIX15_ARB                   0x88CF
#define GL_MATRIX16_ARB                   0x88D0
#define GL_MATRIX17_ARB                   0x88D1
#define GL_MATRIX18_ARB                   0x88D2
#define GL_MATRIX19_ARB                   0x88D3
#define GL_MATRIX20_ARB                   0x88D4
#define GL_MATRIX21_ARB                   0x88D5
#define GL_MATRIX22_ARB                   0x88D6
#define GL_MATRIX23_ARB                   0x88D7
#define GL_MATRIX24_ARB                   0x88D8
#define GL_MATRIX25_ARB                   0x88D9
#define GL_MATRIX26_ARB                   0x88DA
#define GL_MATRIX27_ARB                   0x88DB
#define GL_MATRIX28_ARB                   0x88DC
#define GL_MATRIX29_ARB                   0x88DD
#define GL_MATRIX30_ARB                   0x88DE
#define GL_MATRIX31_ARB                   0x88DF
#endif
/******* end ARB_vertex_program defines *************************/


#define NV_VP_1_0   0
#define NV_VP_1_1   1

/* definitions from d3d8types.h */

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 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)

/* #define GETREGTYPE(x) ((x) & D3DSP_REGTYPE_MASK) */ /* d3d8types.h version */
#define GETREGTYPE(x) (((x) & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT) /* d3d9types.h version */
#define GETREGNUM(x) ((x) & D3DSP_REGNUM_MASK)
#define SETREGNUM(x,y) (((x) & ~D3DSP_REGNUM_MASK) | (y))
#define GETSWIZZLE(x) (((x) & D3DVS_SWIZZLE_MASK) >> D3DVS_SWIZZLE_SHIFT)
#define ISADDRRELATIVE(x) ((x) & D3DVS_ADDRMODE_RELATIVE)
#define ISSRCNEGATIVE(x) (((x) & D3DSP_SRCMOD_MASK) == D3DSPSM_NEG)
#define ISWRITEMASKALL(x) (((x) & D3DSP_WRITEMASK_ALL) == D3DSP_WRITEMASK_ALL)
#define ISWRITEMASKX(x) ((x) & D3DSP_WRITEMASK_0)
#define ISWRITEMASKY(x) ((x) & D3DSP_WRITEMASK_1)
#define ISWRITEMASKZ(x) ((x) & D3DSP_WRITEMASK_2)
#define ISWRITEMASKW(x) ((x) & D3DSP_WRITEMASK_3)

#define GETTOKENTYPE(tok) (((tok) & D3DVSD_TOKENTYPEMASK) >> D3DVSD_TOKENTYPESHIFT)
#define GETCONSTCOUNT(tok) (((tok) & D3DVSD_CONSTCOUNTMASK) >> D3DVSD_CONSTCOUNTSHIFT)
#define GETCONSTADDR(tok) (((tok) & D3DVSD_CONSTADDRESSMASK) >> D3DVSD_CONSTADDRESSSHIFT)
#define GETSTREAMNUM(tok) (((tok) & D3DVSD_STREAMNUMBERMASK) >> D3DVSD_STREAMNUMBERSHIFT)
#define GETVERTEXREG(tok) (((tok) & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT)
#define GETDATATYPE(tok) (((tok) & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT)
#define ISSTREAMTESS(tok) ((tok) & D3DVSD_STREAMTESSMASK)
#define ISSTREAMDATASKIP(tok) ((tok) & D3DVSD_DATALOADTYPEMASK)
#define GETSKIPCOUNT(tok) (((tok) & D3DVSD_SKIPCOUNTMASK) >> D3DVSD_SKIPCOUNTSHIFT)

#define MAX_VERTEX_ATTRIBUTES   16
#define MAX_INSTRUCTIONS       128
#define MAX_INSTRUCTION_LEN     40  /* this is rather arbitrary - for guestimating size */

/* function pointers for implementation specific functions */
void (*dump_opcode)(DWORD opcode) = NULL;
void (*dump_register)(DWORD reg) = NULL;
void (*GL_PrepProgram)(GL_D3D_priv* priv, VertexShader *vs) = NULL;
void (*GL_LoadProgram)(GL_D3D_priv* priv, VertexShader *vs, BOOL inverted) = NULL;


/* some global variables ;-) */
VertexShader *gVs = NULL;

static char *VertexProgram;
static int VPLenCur;
static int VPLenMax;


/* VPConcat
 * Concatenate a string onto the end of the current vertex program, 
 * growing the vertex program if necessary.
 */
inline void VPConcat(const char *str) {
  int len = strlen(str);
  /* grow buffer if necessary */
  while( VPLenCur + len > VPLenMax ) {
    VPLenMax = 2*VPLenMax;
    VertexProgram = HeapReAlloc(GetProcessHeap(), 0, VertexProgram, VPLenMax);
  }

  strncat(VertexProgram, str, len);
  VPLenCur += len;
}

/* VPConcatc
 * Concatenate a char onto the end of the current vertex program, 
 * growing the vertex program if necessary
 */
inline void VPConcatc(const char c) {
  /* grow buffer if necessary */
  while( VPLenCur + 1 > VPLenMax ) {
    VPLenMax = 2*VPLenMax;
    VertexProgram = HeapReAlloc(GetProcessHeap(), 0, VertexProgram, VPLenMax);
  }

  VertexProgram[VPLenCur++] = c;
  VertexProgram[VPLenCur] = '\0';
}
  
/* get_vs_token_len
 * Get the length of the current DX8 vertex shader declaration token
 */
static UINT get_vs_token_len(DWORD tok)
{
  UINT len = 1;  // most tokens have a length of 1 DWORD
  
  switch (GETTOKENTYPE(tok)) {
    /* multiword tokens */
  case D3DVSD_TOKEN_CONSTMEM: 
    len += 4*GETCONSTCOUNT(tok);
    break;
  case D3DVSD_TOKEN_EXT:
    len += (tok & D3DVSD_EXTCOUNTMASK) >> D3DVSD_EXTCOUNTSHIFT;
    break;

    /* single word tokens -- fall-through intentional! */
  case D3DVSD_TOKEN_NOP:  /* NOP Token */
  case D3DVSD_TOKEN_STREAM: /* Stream Selector */
  case D3DVSD_TOKEN_STREAMDATA: /* Stream Data Definition */
  case D3DVSD_TOKEN_TESSELLATOR: /* Stream Data Definition (Tesselator data) */
  case D3DVSD_TOKEN_END:
  default:
    break;
  }
  return len;
}

/* find_vs_constmem 
 * returns a pointer to the next CONSTMEM token in the vertex shader
 * declaration block, or NULL if the end of the declaration block is reached
 */
static const DWORD *find_vs_constmem(const DWORD *pDecl)
{
  DWORD tok;

  while( (tok = GETTOKENTYPE(*pDecl)) != D3DVSD_TOKEN_END ) {
    if( tok == D3DVSD_TOKEN_CONSTMEM ) {
      return pDecl;
    } 
    pDecl += get_vs_token_len(*pDecl);
  }
  return NULL;
}

/* find_vs_stream
 * returns a pointer to the next STREAM token in the vertex shader
 * declaration block, or NULL if the end of the declaration block is reached
 */
static const DWORD *find_vs_stream(const DWORD *pDecl)
{
  DWORD tok;

  while( (tok = GETTOKENTYPE(*pDecl)) != D3DVSD_TOKEN_END ) {
    if( tok == D3DVSD_TOKEN_STREAM ) {
      return pDecl;
    } 
    pDecl += get_vs_token_len(*pDecl);
  }
  return NULL;
}

/* get_datatype_dimensionality
 * returns the number of input DWORDS that are occupied by each 
 * datatype
 */
static UINT get_datatype_dimensionality(DWORD datatype) {
  static const BYTE data_dims[] = {
    [D3DVSDT_FLOAT1] = 1,
    [D3DVSDT_FLOAT2] = 2,
    [D3DVSDT_FLOAT3] = 3,
    [D3DVSDT_FLOAT4] = 4,
    [D3DVSDT_D3DCOLOR] = 1,
    [D3DVSDT_UBYTE4] = 1,
    [D3DVSDT_SHORT2] = 2,
    [D3DVSDT_SHORT4] = 4,
  };
  return data_dims[datatype];
}

/* get_datatype (for debugging)
 * returns a nice printable string corresponding to the given datatype
 */
static const char* const get_datatype(DWORD datatype) {
  static const char* const data_types[] = {
    "D3DVSDT_FLOAT1",
    "D3DVSDT_FLOAT2",
    "D3DVSDT_FLOAT3",
    "D3DVSDT_FLOAT4",
    "D3DVSDT_D3DCOLOR",
    "D3DVSDT_UBYTE4",
    "D3DVSDT_SHORT2",
    "D3DVSDT_SHORT4",
  };
  return data_types[datatype];
}

/* dump_vs_declaration (for debugging)
 * Prints the vertex shader declaration instruction pointed to by pDecl.
 * Returns the number of DWORDS long the parsed declaration was.
 */
static UINT dump_vs_declaration(CONST DWORD* pDecl)
{
  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() ) {
      DPRINTF(" declaration: %08lx NOP()\n", tok);
    } else {
      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 */
      DPRINTF(" declaration: %08lx invalid STREAM token\n", tok);
    } else if (tok & D3DVSD_STREAMTESSMASK) {
      /* Stream Selector: tesselator stream */
      DPRINTF(" declaration: %08lx STREAM Selector: STREAM(TESS)\n", tok);
    } else {
      /* Stream Selector: stream # */
      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 */
        DPRINTF(" declaration: %08lx invalid STREAM Data Def (SKIP) token\n", tok);
      } else {
        /* Data Skip: # of dwords to skip */
        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 */
        DPRINTF(" declaration: %08lx invalid STREAM Data Def (REG)\n", tok);
      } else {
        /* Vertex Input Register Load: data type/dimension, vertex register # */
        DPRINTF(" declaration: %08lx STREAM Data Def: REG(%ld,%s)\n", tok,
                ((tok & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT),
                get_datatype((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 ) {
        DPRINTF(" declaration: %08lx invalid STREAM Data Def (TESSUV) token\n", tok);
      } else {
        /* Tesselator UV data: data type/dimension, Vertex reg out */
        DPRINTF(" declaration: %08lx STREAM Data Def: TESSUV(%s,%ld)\n", tok,
		get_datatype((tok & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT),
		((tok & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT));
      }
    } else {
      if ((tok & ~(D3DVSD_TOKENTYPEMASK|D3DVSD_VERTEXREGINMASK|
		   D3DVSD_DATATYPEMASK|D3DVSD_VERTEXREGMASK)) != 0 ) {
	DPRINTF(" declaration: %08lx invalid STREAM Data Def (TESSNORMAL) token\n", tok);
      } else {
        /* Tesselator normal data: Vertex reg in, data type/dimension, Vertex reg out */
	DPRINTF(" declaration: %08lx STREAM Data Def: TESSNORMAL(%ld,%ld,%s)\n", tok,
		((tok & D3DVSD_VERTEXREGINMASK) >> D3DVSD_VERTEXREGINSHIFT),
                ((tok & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT),
                get_datatype((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 ) {
      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;
      DPRINTF(" declaration: %08lx CONSTMEM(%ld,%ld)\n", tok, count, addr);
      pDecl++;
      for( i=0; i<count; i++ ) {
	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;
      DPRINTF(" declaration: %08lx EXTENSION(%ld,%ld)\n", tok, count, info);
      for( i=0; i<count; i++ ) {
	pDecl++;
	DPRINTF(" declaration:     %08lx\n", *pDecl);
      }
      len += count;
    }
    break;
  case D3DVSD_TOKEN_END:
    if( tok == D3DVSD_END() ) {
      DPRINTF(" declaration: %08lx END()\n", tok);
    } else {
      DPRINTF(" declaration: %08lx invalid END token\n", tok);
    }
    break;
  default:
    DPRINTF(" declaration: %08lx (unknown)\n", tok);
    break;
  }
  return len;
}

/* check_program_version_tag
 * Check the given version opcode to determine if this vertex shader 
 * is a version we support.  Currently supported versions are 1.0 and 1.1
 * Returns TRUE if supported, FALSE otherwise.
 */
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;
}


/* dump_unrecognized_instruction (for debugging)
 * print out sequence of unrecognized vertex shader instructions
 */
static DWORD dump_unrecognised_instruction(const DWORD* program)
{
    DWORD length = 1;

    if (TRACE_ON(ddraw)) DPRINTF("unrecognized ins: %08lX\n", *program);
    program++;

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

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

/* is_comment
 * returns TRUE if opcode is a comment opcode, FALSE otherwise
 */
static BOOL is_comment(DWORD opcode) {
    return ((opcode & D3DSI_OPCODE_MASK) == D3DSIO_COMMENT );
}

/* skip_comment 
 * parse comment in vertex shader function block
 * returns the number of dwords taken by the comment (including the opcode)
 */
static DWORD skip_comment(const DWORD* program)
{
    DWORD len = (*program & D3DSI_COMMENTSIZE_MASK) >> D3DSI_COMMENTSIZE_SHIFT;

    /* assumes the comment is null terminated
       TRACE("COMMENT(%ld): '%s'\n", len, (char *)(program+1)); */

    return (len + 1); /* include the comment opcode itself */
}

/* is_recognized_opcode
 * check if the given opcode is a known DX8 vs1.0/1.1 instruction
 * return true if it is an opcode, false otherwise.
 */
static BOOL is_recognized_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_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];
}

/* num_opcode_operands
 * Returns the number of operands used by the provided
 * VS1.0/1.1 instruction
 */
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];
}

/* dump_writemask
 * Append the writemask specified in the given param
 * to the current vertex program string.
 */
static void dump_writemask(DWORD param)
{
    if( !ISWRITEMASKALL(param) )
    {
	VPConcat(".");

	if (ISWRITEMASKX(param)) VPConcat("x");
	if (ISWRITEMASKY(param)) VPConcat("y");
	if (ISWRITEMASKZ(param)) VPConcat("z");
	if (ISWRITEMASKW(param)) VPConcat("w");
    }
}

/* dump_output_parameter
 * Append the operand destination encoded by param
 * to the current vertex program string.
 * (Appends both the register compenent and any write mask)
 */
static void dump_output_parameter(DWORD param)
{
    dump_register(param);
    dump_writemask(param);
}

/* dump_swizzle
 * Append the swizzle specified in the given param
 * to the current vertex program string.
 */
static void dump_swizzle(DWORD param)
{
    static const char components[] = "xyzw";
    char outbuf[10] = "\0";

    DWORD swizzle = GETSWIZZLE(param);

    if (swizzle == SWIZZLE_NONE) return;

    if (SWIZZLE_X(swizzle) == SWIZZLE_Y(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_Z(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_W(swizzle))
    {
	sprintf(outbuf, ".%c", components[SWIZZLE_X(swizzle)]);
    }
    else
    {
	sprintf(outbuf, ".%c%c%c%c",
		components[SWIZZLE_X(swizzle)],
		components[SWIZZLE_Y(swizzle)],
		components[SWIZZLE_Z(swizzle)],
		components[SWIZZLE_W(swizzle)]);
    }
    VPConcat(outbuf);
}

#ifdef INPUTREG_D3DCOLOR_FIX
/* dump_swizzle_inputcolorfix
 * Append the swizzle specified in the given param
 * to the current vertex program string, but reorder 
 * the components to perform the input color fix
 * 
 * if the input register is of type D3DVSDT_D3DCOLOR, then it is 
 * in the order BGRA and we need it to be in RGBA (ie swap the x,z components)
 */
static void dump_swizzle_inputcolorfix(DWORD param)
{
    static const char components_swap[] = "zyxw";
    char outbuf[10] = "\0";

    DWORD swizzle = GETSWIZZLE(param);

    if (SWIZZLE_X(swizzle) == SWIZZLE_Y(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_Z(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_W(swizzle))
    {
	sprintf(outbuf, ".%c", components_swap[SWIZZLE_X(swizzle)]);
    }
    else
    {
	sprintf(outbuf, ".%c%c%c%c",
		components_swap[SWIZZLE_X(swizzle)],
		components_swap[SWIZZLE_Y(swizzle)],
		components_swap[SWIZZLE_Z(swizzle)],
		components_swap[SWIZZLE_W(swizzle)]);
    }
    VPConcat(outbuf);
}
#endif /* INPUTREG_D3DCOLOR_FIX */

#ifdef OUTPUTREG_D3DCOLOR_FIX
/* if writing the the output color registers, the color
 * values are currently in BGRA order(dx), and the output is expecting them 
 * in RGBA order (gl), so we must reorder them appropriately
 */
static void dump_colorfix_swizzle(DWORD param)
{
    static const char components_fix[] = "zyxw";
    static const char components[] = "xyzw";
    char outbuf[10] = "\0";
    DWORD swizzle = GETSWIZZLE(param);

    if (swizzle == SWIZZLE_NONE) {
	/* reading all the components, this should apply the colorfix */
	TRACE("applying colorfix swizzle to output color register (no swizzle)\n");
	sprintf(outbuf, ".%c%c%c%c",
		components_fix[SWIZZLE_X(swizzle)],
		components_fix[SWIZZLE_Y(swizzle)],
		components_fix[SWIZZLE_Z(swizzle)],
		components_fix[SWIZZLE_W(swizzle)]);
    } 
    else if (SWIZZLE_X(swizzle) == SWIZZLE_Y(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_Z(swizzle)
	&& SWIZZLE_X(swizzle) == SWIZZLE_W(swizzle))
    {
	/* if only one component is specified, it is likely that it is being 
	 * broadcast to all fields, and thus applying the colorfix would 
	 * probably not the be the correct thing to do */
      	TRACE("not applying colorfix swizzle to output color register\n");
	sprintf(outbuf, ".%c", components[SWIZZLE_X(swizzle)]);
    }
    else
    {
	/* reading multiple components, it is likely that we want to reorder
	 * the color components -- but I haven't actually seen a vertex program 
	 * that uses this case yet */
	TRACE("applying colorfix swizzle to output color register (swizzle)\n");
	sprintf(outbuf, ".%c%c%c%c",
		components_fix[SWIZZLE_X(swizzle)],
		components_fix[SWIZZLE_Y(swizzle)],
		components_fix[SWIZZLE_Z(swizzle)],
		components_fix[SWIZZLE_W(swizzle)]);
    }
    VPConcat(outbuf);
}
#endif /* OUTPUTREG_D3DCOLOR_FIX */ 

/* dump_input_parameter
 * Append the operand source encoded by param
 * to the current vertex program string.
 * Appends both the register compenent and any necessary swizzling.
 */
static void dump_input_parameter(DWORD param)
{
    if (ISSRCNEGATIVE(param)) {
      VPConcat("-");
    }
    dump_register(param);
#ifdef INPUTREG_D3DCOLOR_FIX
    /* special case if input stream is D3DVSTD_D3DCOLOR */
    if( GETREGTYPE(param) == D3DSPR_INPUT && 
	GET_INPUT_REG_TYPE(gVs->input_reg[GETVERTEXREG(param)].type) == D3DVSDT_D3DCOLOR ) {
      dump_swizzle_inputcolorfix(param);
    } else 
#endif
    {
      dump_swizzle(param);
    }
}

/* dump_neg_input_parameter
 * Append the negative operand source encoded by param
 * to the current vertex program string.
 * Appends both the register compenent and any necessary swizzling.
 * Same as dump_input_parameter except this first negates the param.
 */
static void dump_neg_input_parameter(DWORD param)
{
    if (!ISSRCNEGATIVE(param)) {
      VPConcat("-");
    }
    dump_register(param);
#ifdef INPUTREG_D3DCOLOR_FIX    
    /* special case if input stream is D3DVSTD_D3DCOLOR */
    if( GETREGTYPE(param) == D3DSPR_INPUT && 
	GET_INPUT_REG_TYPE(gVs->input_reg[GETVERTEXREG(param)].type) == D3DVSDT_D3DCOLOR ) {
      dump_swizzle_inputcolorfix(param);
    } else
#endif     
    {
      dump_swizzle(param);
    }
}

/* translate_scalar_op
 * Translate the scalar vs1.x instruction into the equivalent VP instruction.
 * If the vs instruction does not specify an explicit scalar source, defaultsubscipt is 
 * added in to the vp instruction (which requires an explicit scalar source).
 */
static void translate_scalar_op(DWORD opcode, DWORD dst, DWORD src, char *defaultsubscript) 
{
  dump_opcode(opcode);
  dump_output_parameter(dst); VPConcat(", ");
  dump_input_parameter(src); 
  if( GETSWIZZLE(src) == SWIZZLE_NONE ) {
    /* add the default source subscript (as provided) */
    VPConcat(defaultsubscript);
  }
  VPConcat(";\n");
}

/* translate_mov_op
 * Translate the vs1.x mov instruction into the equivalent VP instruction.
 * This can be either ARL or MOV, depending on the destination register
 */
static void translate_mov_op(DWORD dst, DWORD src) 
{
  /* could be MOV or ARL, depending on dest */
  if( GETREGTYPE(dst) == D3DSPR_ADDR ) { 
    VPConcat("ARL "); 
    /* not sure if DX8-vs dst & src mask are implicit or explicit, so check */
    dump_output_parameter(dst);
    if( ISWRITEMASKALL(dst) ) {
      /* add the explicit .x writemask, if not specified */
      VPConcat(".x");
    }
    VPConcat(", ");
    dump_input_parameter(src);
    if( GETSWIZZLE(src) == SWIZZLE_NONE ){
      /* add the default source subscript (.x) */
      VPConcat(".x");
    } 
    VPConcat(";\n");
  } else {
    dump_opcode(D3DSIO_MOV);
    dump_output_parameter(dst); VPConcat(", "); 
#ifdef OUTPUTREG_D3DCOLOR_FIX	
    /* if writing to color output register we must apply the BGRA->RGBA colorfix */
    if( GETREGTYPE(dst) == D3DSPR_ATTROUT ) {
      if (ISSRCNEGATIVE(src)) VPConcat("-");
      dump_register(src);
      dump_colorfix_swizzle(src);
    } else  
#endif	  
      dump_input_parameter(src); 
    VPConcat(";\n");
  }
}  

/* increment_register 
 * Returns the encoded register immediately following reg in the 
 * same register file.
 */
static DWORD increment_register(DWORD reg) {
  DWORD newreg;
  DWORD regnum = GETREGNUM(reg);
  regnum++;
  newreg = SETREGNUM(reg, regnum);
  return newreg;
}

/* expand matrix transforms macros 
 * - implemented using DP3/DP4
 * - writemask, negation or swizzle modifiers cannot be used */
static void translate_matrix_macro(DWORD dst, const DWORD *src, int rows, int cols)
{
  static const char * const components = "xyzw";
  DWORD opcode, src1;
  int i; 
  char buf[100] = "\0";

  sprintf(buf, "# expanding 'm%dx%d ", rows, cols);
  VPConcat(buf);
  dump_output_parameter(dst); VPConcat(", ");
  dump_input_parameter(src[0]); VPConcat(", ");
  dump_input_parameter(src[1]); VPConcat(";'\n");
  
  opcode = (rows == 3) ? D3DSIO_DP3 : D3DSIO_DP4;
  src1 = src[1];
  for( i=0; i<cols; i++ ) {
    dump_opcode(opcode);
    dump_register(dst); 
    VPConcat("."); 
    VPConcatc(components[i]); 
    VPConcat(", ");
    if (ISSRCNEGATIVE(src[0])) VPConcat("-");
    dump_register(src[0]); 
    VPConcat(", ");
    if (ISSRCNEGATIVE(src1)) VPConcat("-");
    dump_register(src1); 
    VPConcat(";\n");
    src1 = increment_register(src1);
  }

}

/* ============ NV_vertex_program specific functions =====================*/

/* dump_opcode_nv
 * Append the NV_vp instruction equivalent to the VS1.0/1.1 instruction 
 * opcode onto the current vertex program string.
 */
static void dump_opcode_nv(DWORD opcode)
{
    static const char * const opcode_names[] =
	{
	    [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_EXPP] = "EXP ",
	    [D3DSIO_LOGP] = "LOG "
	};

    VPConcat(opcode_names[opcode]);
}

/* dump_register_nv
 * Append the NV_vp register name equivalent to the VS1.0/1.1 register
 * onto the current vertex program string
 */
static void dump_register_nv(DWORD reg)
{
  static const char * const rastout_names[] = { "o[HPOS]", "o[FOGC]", "o[PSIZ]" };
  static const char * const attrout_names[] = { "o[COL0]", "o[COL1]" };
  char outbuf[20] = "\0";
  
  DWORD regnum = GETREGNUM(reg);
  
  switch (GETREGTYPE(reg)) {
  case D3DSPR_TEMP:
    sprintf(outbuf, "R%lu", regnum);
    break;
    
  case D3DSPR_INPUT:
    sprintf(outbuf, "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. 
     * (and how are negative values handled??)
     */
    sprintf(outbuf, "c[%s%lu]",
	    ISADDRRELATIVE(reg) ? "A0.x+" : "",
	    regnum);
    break;
    
  case D3DSPR_ADDR:
    sprintf(outbuf, "A%lu", regnum);
    break;
    
  case D3DSPR_RASTOUT:
    sprintf(outbuf, "%s", rastout_names[regnum]);
    break;
    
  case D3DSPR_ATTROUT:
    sprintf(outbuf, "%s", attrout_names[regnum]);
    break;
    
  case D3DSPR_TEXCRDOUT:
    sprintf(outbuf, "o[TEX%lu]", regnum);
    break;

  default:
    TRACE("unknown register\n");
  }

  VPConcat(outbuf);
}

/* translate_instruction_nv
 * Translate a single vs1.x instruction to the equivalent NV_vp instruction
 * and concatenate onto the end of the current vertex program string
 */
static DWORD translate_instruction_nv(const DWORD* program, int version)
{
  static const char components[] = "xyzw";
  if (is_recognized_opcode(*program)) {
    DWORD i;
    DWORD opcode = *program & D3DSI_OPCODE_MASK;
    DWORD num_params = num_opcode_operands(opcode);
    DWORD dst = program[1];
    const DWORD *src = &(program[2]);

    switch(opcode) {
    case D3DSIO_NOP:  /* no need for this op */
      break; 
    case D3DSIO_MOV: 
      translate_mov_op(dst, src[0]);
      break;

      /* SCALAR ops -- translate directly to NV1.0/1.1
       * but may need to add an explicit source subscript
       * RCP/RSQ specify default of .x, but EXPP/LOGP seem to use .w  */
    case D3DSIO_RCP:  
    case D3DSIO_RSQ:
      translate_scalar_op(opcode, dst, src[0], ".x"); break;
    case D3DSIO_EXPP:
    case D3DSIO_LOGP:
      translate_scalar_op(opcode, dst, src[0], ".w"); break;
      
      /* these ops translate directly to NV1.0/1.1 */
    case D3DSIO_ADD:
    case D3DSIO_MAD:
    case D3DSIO_MUL:
    case D3DSIO_DP3:
    case D3DSIO_DP4:
    case D3DSIO_MIN:
    case D3DSIO_MAX:
    case D3DSIO_SLT:
    case D3DSIO_SGE:
    case D3DSIO_LIT:
    case D3DSIO_DST: 
      dump_opcode(opcode);
      
      if( num_params > 0 ) dump_output_parameter(dst);
      for( i=0; i<num_params-1; i++ ) {
	VPConcat(", ");
	dump_input_parameter(src[i]);
      }
      VPConcat(";\n");
      break;

    case D3DSIO_SUB:  /* supported on NV1.1, but simulated on NV1.0 */
      if( version == NV_VP_1_1 ) {
	/* SUB dst, src0, src1 */
	dump_opcode(D3DSIO_SUB);
	dump_output_parameter(dst); VPConcat(", "); 
	dump_input_parameter(src[0]); VPConcat(", "); 
	dump_input_parameter(src[1]); VPConcat(";\n");
      } else {
	/* ADD dst, src0, -src1 */
	dump_opcode(D3DSIO_ADD);
	dump_output_parameter(dst); VPConcat(", "); 
	dump_input_parameter(src[0]); VPConcat(", "); 
	dump_neg_input_parameter(src[1]); VPConcat(";\n");
      }
      break;

      /* Matrix transforms macros - implemented using DP3/DP4
	 writemask, negation or swizzle modifiers cannot be used */
    case D3DSIO_M3x2: /* 2D matrix transform */
      translate_matrix_macro(dst, src, 3, 2); break;
    case D3DSIO_M3x3: /* 3D matrix transform - no w */
      translate_matrix_macro(dst, src, 3, 3); break;
    case D3DSIO_M3x4: /* 3D matrix transform - no translation */
      translate_matrix_macro(dst, src, 3, 4); break;
    case D3DSIO_M4x3: /* 3D matrix transform - no projective */
      translate_matrix_macro(dst, src, 4, 3); break;
    case D3DSIO_M4x4: /* 3D projective tranform */
      translate_matrix_macro(dst, src, 4, 4); break;
     
    case D3DSIO_FRC: 
      /* FRC dst, src -- computes fraction components of x&y components
       * emulate using sequence of EXP and MOV
       * (Only the .y and .xy write masks seem to be valid in DX8)
       */
      {
	DWORD swzl = GETSWIZZLE(src[0]); 
	VPConcat("# emulating: 'FRC ");
	dump_output_parameter(dst); VPConcat(", ");
	dump_input_parameter(src[0]); VPConcat(";'\n");
	if( ISWRITEMASKX(dst) && ISWRITEMASKY(dst) ) {
	  int X_is_y = ( components[SWIZZLE_X(swzl)] == 'y' );
	  int src_is_dst = ( GETREGTYPE(src[0]) == GETREGTYPE(dst) &&
			   GETREGNUM(src[0]) == GETREGNUM(dst) );
	  
 	  if( SWIZZLE_X(swzl) == SWIZZLE_Y(swzl) ) { 
	    /* special case: src X,Y are the same -- save an instruction*/
	    VPConcat("EXP ");
	    dump_register(dst); VPConcat(".y, ");
	    if (ISSRCNEGATIVE(src[0])) VPConcat("-");
	    dump_register(src[0]); VPConcat(".");
	    VPConcatc(components[SWIZZLE_X(swzl)]);

	    VPConcat(";\nMOV ");
	    dump_register(dst); VPConcat(".x, ");
	    dump_register(dst); VPConcat(".y;\n");
	    
	  } else if( src_is_dst ) {
	    /* special case: src,dst registers are the same --
	     * must ensure that the y component of the register is used first
	     * and may need an extra swap */
	    VPConcat("EXP "); 
	    dump_register(dst); VPConcat(".y, ");
	    if (ISSRCNEGATIVE(src[0])) VPConcat("-");
	    dump_register(src[0]); VPConcat(".");
	    if( X_is_y ) {
	      VPConcatc(components[SWIZZLE_X(swzl)]);
	    } else {
	      VPConcatc(components[SWIZZLE_Y(swzl)]);
	    }
	    
	    VPConcat(";\nMOV ");
	    dump_register(dst); VPConcat(".xy, ");
	    dump_register(dst); VPConcat(".y");
	    if( X_is_y ) {
	      VPConcatc(components[SWIZZLE_Y(swzl)]);
	    } else {
	      VPConcatc(components[SWIZZLE_X(swzl)]);
	    }
	    
	    VPConcat(";\nEXP ");
	    dump_register(dst); VPConcat(".y, ");
	    if (ISSRCNEGATIVE(src[0])) VPConcat("-");
	    dump_register(dst); VPConcat(".");
	    VPConcatc(components[SWIZZLE_Y(swzl)]); 
	    VPConcat(";\n");
	    if( !X_is_y ) {
	      VPConcat("MOV ");
	      dump_register(dst); VPConcat(".xy, ");
	      dump_register(dst); VPConcat(".yx;\n");
	    }
	    
	  } else {
	    /* general case */
	    VPConcat("EXP "); 
	    dump_register(dst); VPConcat(".y, ");
	    if (ISSRCNEGATIVE(src[0])) VPConcat("-");
	    dump_register(src[0]); VPConcat(".");
	    VPConcatc(components[SWIZZLE_X(swzl)]);
	    
	    VPConcat(";\nMOV ");
	    dump_register(dst); VPConcat(".x, ");
	    dump_register(dst); VPConcat(".y;\n");
	    
	    VPConcat("EXP ");
	    dump_register(dst); VPConcat(".y, ");
	    if (ISSRCNEGATIVE(src[0])) VPConcat("-");
	    dump_register(src[0]); VPConcat(".");
	    VPConcatc(components[SWIZZLE_Y(swzl)]); 
	    VPConcat(";\n");
	  }
	  
	} else if( ISWRITEMASKY(dst) ) {
	  VPConcat("EXP ");
	  dump_register(dst); VPConcat(".y, ");
	  if (ISSRCNEGATIVE(src[0])) VPConcat("-");
	  dump_register(src[0]); VPConcat(".");
	  VPConcatc(components[SWIZZLE_X(swzl)]);
	  VPConcat(";\n");
	}
	VPConcat("# end emulation\n");
      }
      break;

      /* High precision ops -- must be emulated on NV1.0/1.1
       * To properly simulate these instructions requires
       * 2-3 constants, 1 temp register, and 10 instructions, as 
       * explained in the NVIDIA OpenGL Extension specs.
       * Since this would be very difficult, we'll just implement
       * by calling the low precision versions, and replicating 
       * the results as necessary (and hope it works!) */
    case D3DSIO_EXP: 
    case D3DSIO_LOG:
      WARN("Emulating high precision EXP/LOG--may have unexpected side effects!!\n");
      VPConcat("# Emulating high precision op, with low precision op\n");
      dump_opcode(opcode); 
      dump_register(dst); VPConcat(".z, ");
      dump_input_parameter(src[0]); VPConcat(";\n");
      VPConcat("MOV "); 
      dump_output_parameter(dst); VPConcat(", ");
      dump_register(dst); VPConcat(".z;\n");
      break;
      
    default:
      TRACE("NV instruction %08lx not implemented yet\n", opcode);
    }

    /* flip check - if we are inverting, flip the y-coord before it is written to 
     * the oPos register.  This inserts a new instruction that rewrites the 
     * output y coordinate */
    if(gVs->inverted && GETREGTYPE(dst) == D3DSPR_RASTOUT && GETREGNUM(dst) == D3DSRO_POSITION) {
      if( ISWRITEMASKALL(dst) || ISWRITEMASKY(dst) ) {
	DWORD newinst[5];
	newinst[0] = program[0]; /* opcode */
	/* tweak newinst[1] so that only .y is written */
	newinst[1] = (dst & ~D3DSP_WRITEMASK_ALL) | D3DSP_WRITEMASK_1;
	/* tweak newinst[2] so that it is negated */
	if( ISSRCNEGATIVE(src[0]) ) {
	  newinst[2] = src[0] & ~D3DSPSM_NEG;
	} else {
	  newinst[2] = src[0] | D3DSPSM_NEG;
	}
	for( i=1; i<num_params-1; i++ ) {
	  newinst[2+i] = src[i];
	}

	gVs->inverted = FALSE;
	translate_instruction_nv(newinst, version);
	gVs->inverted = TRUE;
      }
    }

    return num_params + 1;

  } else if (is_comment(*program)) {
    return skip_comment(program);

  } else if (*program == D3DSIO_DEF) {
    return 6;
  } else if (*program == D3DSIO_DCL) {
    return 3;
  } else {
    return dump_unrecognised_instruction(program);
  }
}

/* GL_LoadProgram_nv
 * (re)load the program string and check for errors.
 * if inverted is set, the program string with the y-coordinate flipped is loaded
 * otherwise, the normal program string is loaded.
 */
static void GL_LoadProgram_nv(GL_D3D_priv* priv, VertexShader *vs, BOOL inverted)
{
  GLint errpos;
  GLuint name = (GLuint)vs->DriverReserved0;
  char *programstring = vs->programstring[inverted?1:0];

  vs->inverted = inverted;
  GLD(priv)->glLoadProgramNV(GL_VERTEX_PROGRAM_NV, name, 
			     strlen(programstring), (GLubyte *)programstring);

  glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &errpos);
  if( errpos < 0 ) {
    TRACE("NV: vertex program (name=%u,inverted=%d) loaded successfully\n", name, inverted);
  } else if( errpos < strlen(programstring) ) {
    WARN("NV: syntax error in vertex program at offset=%d\n", errpos);
    if(TRACE_ON(ddraw)) fprintf(stderr, "%s", &programstring[errpos]);
  } else {
    WARN("NV: semantic error in vertex program errpos=%d\n", errpos);
  }
}

/* GL_PrepProgram_nv
 * Load the program string for the vertex program in this context 
 */
static void GL_PrepProgram_nv(GL_D3D_priv* priv, VertexShader *vs) 
{
  GLuint name = 0;

  /* store which context the program belongs to */
  vs->DriverReserved1 = priv->d3dp->dwhContext;
  
  /* generated a name(id) for the program */
  GLD(priv)->glGenProgramsNV(1, &name);
  TRACE("vs=%p, generated program name=%u\n", vs, name);

  /* save the vertex program name(id) for later use */
  vs->DriverReserved0 = (DWORD)name;
  
  /* load the vertex program and check for errors */
  GL_LoadProgram_nv(priv, vs, FALSE);

  CHECK_GL_ERROR("PrepProgram_nv");
}

/* GL_VP_create_NV
 * create an NV vertex program (using version) that corresponds to the 
 * provided VS1.x vertex shader
 */
static void GL_VP_create_NV(GL_D3D_priv* priv, VertexShader *vs, int version) {
  const DWORD * program;
  int i;
  vs->inverted = FALSE;

  for( i=0; i<2; i++ ) {
    program = vs->pFunction;

    /* initialize things for the vertex program string */
    VPLenMax = MAX_INSTRUCTIONS * MAX_INSTRUCTION_LEN;
    VPLenCur = 0;
    VertexProgram = HeapAlloc(GetProcessHeap(), 0, VPLenMax*sizeof(char));
    VertexProgram[0] = '\0';

    /* add NV version string */
    switch (version) {
    case NV_VP_1_0: 
      VPConcat("!!VP1.0\n");
      break;
    case NV_VP_1_1:
      VPConcat("!!VP1.1\n");
      break;
    }

    program++;  /* skip the version header */
    gVs = vs;

    while (*program != D3DVS_END()) {
      program += translate_instruction_nv(program, version);
    }
    VPConcat("END\n");
    gVs = NULL;

    if( TRACE_ON(ddraw) ) {
      fprintf(stderr, "NV vertex program string:\n%s", VertexProgram);
    }

    /* save the program string for loading later */
    vs->programstring[i] = VertexProgram;
    vs->inverted = !vs->inverted;
  }

  VertexProgram=NULL;
  VPLenCur=0;
  
}

/* ============ ARB_vertex_program specific functions =====================*/

/* dump_opcode_arb
 * function to concatenate the ARB_vp instruction equivalent of 
 * the VS1.0/1.1 instruction onto the current vertex program string
 */
static void dump_opcode_arb(DWORD opcode)
{
    static const char * const opcode_names[] =
	{
	    [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] = "EX2 ",
	    [D3DSIO_LOG] = "LG2 ",
	    [D3DSIO_LIT] = "LIT ",
	    [D3DSIO_DST] = "DST ",
	    [D3DSIO_FRC] = "FRC ",
	    [D3DSIO_EXPP] = "EXP ",
	    [D3DSIO_LOGP] = "LOG "
	};

    VPConcat(opcode_names[opcode]);
}

/* dump_register_arb
 * Append the ARB_vp register name equivalent to the VS1.0/1.1 register
 * onto the current vertex program string
 */
static void dump_register_arb(DWORD reg)
{
  static const char * const rastout_names[] = { "result.position", 
                                                "result.fogcoord", 
						"result.pointsize" };
  static const char * const attrout_names[] = { "result.color.primary", 
                                                "result.color.secondary" };
  char outbuf[100] = "\0";
  
  DWORD regnum = GETREGNUM(reg);
  
  switch (GETREGTYPE(reg)) {
  case D3DSPR_TEMP:
    sprintf(outbuf, "R%lu", regnum);
    break;
    
  case D3DSPR_INPUT:
    sprintf(outbuf, "vertex.attrib[%lu]", regnum);
    break;
    
  case D3DSPR_CONST:
    /* XXX What is the correct interpretation of regnum when a relative
     * offset is used?
     * GL_ARB_vertex_program uses the range -64 to 63. 
     * (and how are negative values handled??)
     */
    sprintf(outbuf, "c[%s%lu]",
	    ISADDRRELATIVE(reg) ? "A0.x+" : "",
	    regnum);
    break;
    
  case D3DSPR_ADDR:
    sprintf(outbuf, "A%lu", regnum);
    break;
    
  case D3DSPR_RASTOUT:
    sprintf(outbuf, "%s", rastout_names[regnum]);
    break;
    
  case D3DSPR_ATTROUT:
    sprintf(outbuf, "%s", attrout_names[regnum]);
    break;
    
  case D3DSPR_TEXCRDOUT:
    sprintf(outbuf, "result.texcoord[%lu]", regnum);
    break;

  default:
    TRACE("unknown register\n");
  }

  VPConcat(outbuf);
}

/* translate_instruction_arb
 * Translate a single vs1.x instruction to the equivalent ARB_vp instruction
 * and concatenate onto the end of the current vertex program string
 */
static DWORD translate_instruction_arb(const DWORD* program)
{
  if (is_recognized_opcode(*program)) {
    DWORD i;
    DWORD opcode = *program & D3DSI_OPCODE_MASK;
    DWORD num_params = num_opcode_operands(opcode);
    DWORD dst = program[1];
    const DWORD *src = &(program[2]);

    switch(opcode) {
    case D3DSIO_NOP:  /* no need for this op */
      break; 
    case D3DSIO_MOV:
      translate_mov_op(dst, src[0]);
      break;

      /* SCALAR ops -- translate directly to ARB
       * but may need to add an explicit source subscript
       * RCP/RSQ specify default of .x, but EXPP/LOGP seem to use .w  */
    case D3DSIO_RCP:  
    case D3DSIO_RSQ:  
      translate_scalar_op(opcode, dst, src[0], ".x"); break;
    case D3DSIO_EXPP:
    case D3DSIO_LOGP:
      translate_scalar_op(opcode, dst, src[0], ".w"); break;
      
      /* these ops translate directly to ARB */
    case D3DSIO_ADD:
    case D3DSIO_MAD:
    case D3DSIO_MUL:
    case D3DSIO_DP3:
    case D3DSIO_DP4:
    case D3DSIO_MIN:
    case D3DSIO_MAX:
    case D3DSIO_SLT:
    case D3DSIO_SGE:
    case D3DSIO_LIT:
    case D3DSIO_DST: 
    case D3DSIO_SUB:  
    case D3DSIO_FRC: 
    case D3DSIO_EXP: 
    case D3DSIO_LOG:
      dump_opcode(opcode);
      
      if( num_params > 0 ) dump_output_parameter(dst);
      for( i=0; i<num_params-1; i++ ) {
	VPConcat(", ");
	dump_input_parameter(src[i]);
      }
      VPConcat(";\n");
      break;

      /* Matrix transforms macros - implemented using DP3/DP4
	 writemask, negation or swizzle modifiers cannot be used */
    case D3DSIO_M3x2: /* 2D matrix transform */
      translate_matrix_macro(dst, src, 3, 2); break;
    case D3DSIO_M3x3: /* 3D matrix transform - no w */
      translate_matrix_macro(dst, src, 3, 3); break;
    case D3DSIO_M3x4: /* 3D matrix transform - no translation */
      translate_matrix_macro(dst, src, 3, 4); break;
    case D3DSIO_M4x3: /* 3D matrix transform - no projective */
      translate_matrix_macro(dst, src, 4, 3); break;
    case D3DSIO_M4x4: /* 3D projective tranform */
      translate_matrix_macro(dst, src, 4, 4); break;
      
    default:
      TRACE("ARB instruction %08lx not implemented yet\n", opcode);
    }

    /* flip check - if we are inverting, flip the y-coord before it is written to 
     * the oPos register.  This inserts a new instruction that rewrites the 
     * output y coordinate */
    if(gVs->inverted && GETREGTYPE(dst) == D3DSPR_RASTOUT && GETREGNUM(dst) == D3DSRO_POSITION) {
      if( ISWRITEMASKALL(dst) || ISWRITEMASKY(dst) ) {
	DWORD newinst[5];
	newinst[0] = program[0]; /* opcode */
	/* tweak newinst[1] so that only .y is written */
	newinst[1] = (dst & ~D3DSP_WRITEMASK_ALL) | D3DSP_WRITEMASK_1;
	/* tweak newinst[2] so that it is negated */
	if( ISSRCNEGATIVE(src[0]) ) {
	  newinst[2] = src[0] & ~D3DSPSM_NEG;
	} else {
	  newinst[2] = src[0] | D3DSPSM_NEG;
	}
	for( i=1; i<num_params-1; i++ ) {
	  newinst[2+i] = src[i];
	}

	gVs->inverted = FALSE;
	translate_instruction_arb(newinst);
	gVs->inverted = TRUE;
      }
    }

    return num_params + 1;

  } else if (is_comment(*program)) {
    return skip_comment(program);

  } else if (*program == D3DSIO_DEF) {
    return 6;
  } else if (*program == D3DSIO_DCL) {
    return 3;
  } else {
    return dump_unrecognised_instruction(program);
  }
}

/* GL_LoadProgram_arb
 * (re)load the program string and check for errors.
 * if inverted is set, the program string with the y-coordinate flipped is loaded
 * otherwise, the normal program string is loaded.
 */
static void GL_LoadProgram_arb(GL_D3D_priv* priv, VertexShader *vs, BOOL inverted)
{
  GLint errpos;
  GLuint name = (GLuint)vs->DriverReserved0;
  char *programstring = vs->programstring[inverted?1:0];

  vs->inverted = inverted;
  GLD(priv)->glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, 
			     strlen(programstring), (GLvoid *)programstring);

  glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errpos);
  if( errpos < 0 ) {
    TRACE("ARB: vertex program (name=%u,inverted=%d) loaded successfully\n", name, inverted);
  } else {
    const GLubyte *errstr = glGetString(GL_PROGRAM_ERROR_STRING_ARB);
    WARN("ARB: error string='%s'\n", errstr);
    if( errpos < strlen(programstring) ) {
      WARN("ARB: syntax error in vertex program at offset=%d\n", errpos);
      if(TRACE_ON(ddraw)) fprintf(stderr, "%s", &programstring[errpos]);
    } else {
      WARN("ARB: semantic error in vertex program errpos=%d\n", errpos);
    }
  }
}
 
/* GL_PrepProgram_arb
 * Load the program string for the vertex program in this context 
 */
static void GL_PrepProgram_arb(GL_D3D_priv* priv, VertexShader *vs) 
{
  GLuint last, name = 0;

  /* store which context the program belongs to */
  vs->DriverReserved1 = priv->d3dp->dwhContext;
  
  /* generated a name(id) for the program */
  GLD(priv)->glGenProgramsARB(1, &name);
  TRACE("vs=%p, generated program name=%u\n", vs, name);

  /* save the vertex program name(id) for later use */
  vs->DriverReserved0 = (DWORD)name;
  
  /* save currently bound program object */
  GLD(priv)->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &last);
  
  /* bind and load the new vertex program */
  GLD(priv)->glBindProgramARB(GL_VERTEX_PROGRAM_ARB, name);
  GL_LoadProgram_arb(priv, vs, FALSE);
  
  /* restore previously bound program object */
  GLD(priv)->glBindProgramARB(GL_VERTEX_PROGRAM_ARB, last);

  CHECK_GL_ERROR("PrepProgram_arb");
}

/* GL_VP_create_ARB
 * create an ARB vertex program that corresponds to the 
 * provided VS1.x vertex shader
 */
static void GL_VP_create_ARB(GL_D3D_priv* priv, VertexShader *vs) {
  const DWORD * program;
  int i;
  vs->inverted = FALSE;
  
  for( i=0; i<2; i++ ) {
    program = vs->pFunction;

    /* initialize things for the vertex program string */
    VPLenMax = MAX_INSTRUCTIONS * MAX_INSTRUCTION_LEN;
    VPLenCur = 0;
    VertexProgram = HeapAlloc(GetProcessHeap(), 0, VPLenMax*sizeof(char));
    VertexProgram[0] = '\0';

    /* add ARB version string */
    VPConcat("!!ARBvp1.0\n");

    program++;  /* skip the version header */
    gVs = vs;

    /* add declaration block */
    VPConcat("ADDRESS A0;\n");
    VPConcat("TEMP R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11;\n");
    VPConcat("PARAM c[96] = { program.env[0..95] };\n");
   
    while (*program != D3DVS_END()) {
      program += translate_instruction_arb(program);
    }
    VPConcat("END\n");
    gVs = NULL;

    if( TRACE_ON(ddraw) ) {
      fprintf(stderr, "ARB vertex program string:\n%s", VertexProgram);
    }

    /* save the program string for loading later */
    vs->programstring[i] = VertexProgram;
    vs->inverted = !vs->inverted;
  }
  
  VertexProgram=NULL;
  VPLenCur=0;
  
}


/* =================== (Internal) Vertex Program API =====================
 * Used internally to manage vertex programs independently of 
 * the vertex program extension being used (if in fact one is present).
 * Currently this supports NV_vertex_program (and 1_1) and ARB_vertex_program
 * ======================================================================= */

/* GL_VP_create
 * Create a vertex program from the provided VertexShader (vs)
 */
static HRESULT 
GL_VP_create(GL_D3D_priv* priv, VertexShader *vs)
{
  DWORD vs_version = vs->pFunction[0];

  /* make sure we have a recognized shader version */
  if( !check_program_version_tag(vs_version) ) {
    TRACE("unrecognized vertex shader version 0x%08lx\n", vs_version);
    TRACE("vs.%ld.%ld\n", D3DSHADER_VERSION_MAJOR(vs_version),
	  D3DSHADER_VERSION_MINOR(vs_version));
    return D3DERR_INVALIDCALL;
  }

  /* check what GL extension we are using */
  if( HAVE_GL_EXT(priv, ARB_vertex_program) ) {
    /* when implemented this should be the default one! */
    TRACE("using ARB_vertex_program\n");
    
    /* convert DX8VertexShader function block into ARB vertex program string */
    GL_VP_create_ARB(priv, vs); 

  } else if( HAVE_GL_EXT(priv, NV_vertex_program1_1) || HAVE_GL_EXT(priv, NV_vertex_program) ) {
    int version;
    
    if( HAVE_GL_EXT(priv, NV_vertex_program1_1) ) {
      TRACE("using NV_vertex_program1_1\n");
      version = NV_VP_1_1;
    } else {
      TRACE("using NV_vertex_program\n");
      version = NV_VP_1_0;
    }
    
    /* convert DX8VertexShader function block into NV vertex program definition */
    GL_VP_create_NV(priv, vs, version); 
    
  }
  return D3D_OK;
}

/* GL_VP_set_constant
 * Set the global vertex program constant parameters.
 * Fill in 'count' 4-component registers starting at 'reg'.
 */
static void 
GL_VP_set_constant(GL_D3D_priv* priv, GLuint reg, GLuint count, float *consts) 
{
  if(HAVE_GL_EXT(priv, ARB_vertex_program) ) {
    GLuint i;
    TRACE("ARB: setting vertex program constants reg=%d, count=%d\n", reg, count);
    for( i=0; i<count; i++ ) {
#if 0
      /* code block is for debugging -- writes to each register and reads the
       * value back to confirm that it is set correctly */
      GLfloat set[4];
      TRACE("  set reg=%d, (%f,%f,%f,%f)\n", reg+i, 
	  consts[i*4+0], consts[i*4+1], consts[i*4+2], consts[i*4+3]);
      GLD(priv)->glProgramEnvParameter4fARB(GL_VERTEX_PROGRAM_ARB, reg+i,
	  consts[i*4+0], consts[i*4+1], consts[i*4+2], consts[i*4+3]);

      /* read back the values from the registers to see if they were set properly */
      GLD(priv)->glGetProgramEnvParameterfvARB(GL_VERTEX_PROGRAM_ARB, reg+i, set);
      TRACE("  got reg=%d, (%f,%f,%f,%f)\n", reg+i, 
	  set[0], set[1], set[2], set[3]);
#else
      GLD(priv)->glProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 
	                                     reg+i, &(consts[i*4]));
#endif 
    }
  } else if(HAVE_GL_EXT(priv, NV_vertex_program) ) {
#if 0    
    /* this code block is for debugging -- writes the registers one at a time 
     * and checks the values in the register after the write */
    GLuint i;
    TRACE("NV: setting vertex program constants reg=%d, count=%d\n", reg, count);
    for( i=0; i<count; i++ ) {
      GLfloat set[4];
      TRACE("  set reg=%d, (%f,%f,%f,%f)\n", reg+i, 
	  consts[i*4+0], consts[i*4+1], consts[i*4+2], consts[i*4+3]);
      GLD(priv)->glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, reg+i,
	  consts[i*4+0], consts[i*4+1], consts[i*4+2], consts[i*4+3]);

      /* read back the values from the registers to see if they were set properly */
      GLD(priv)->glGetProgramParameterfvNV(GL_VERTEX_PROGRAM_NV, reg+i, 
	                                 GL_PROGRAM_PARAMETER_NV, set);
      TRACE("  got reg=%d, (%f,%f,%f,%f)\n", reg+i, 
	  set[0], set[1], set[2], set[3]);
    }
#else
    TRACE("NV: setting vertex program constants reg=%d, count=%d\n", reg, count);
    GLD(priv)->glProgramParameters4fvNV(GL_VERTEX_PROGRAM_NV, 
					reg, count, consts); 
#endif
  }
  CHECK_GL_ERROR("VP_set_constant");
}

/* GL_VP_enable
 * Enable vertex programs and set the vertex program 'name' as the 
 * current vertex program.
 */
static void 
GL_VP_enable(GL_D3D_priv* priv, GLuint name) 
{
  if( HAVE_GL_EXT(priv, ARB_vertex_program) ) {
    TRACE("ARB: enabling vertex programs and setting to name=%u\n", name);
     
    GLD(priv)->glBindProgramARB(GL_VERTEX_PROGRAM_ARB, name);
    if (!priv->isVertexProgramEnabled) {
      glEnable(GL_VERTEX_PROGRAM_ARB);
      priv->isVertexProgramEnabled = TRUE;
    }
    
  } else if( HAVE_GL_EXT(priv, NV_vertex_program) ) {
    TRACE("NV: enabling vertex programs and setting to name=%u\n", name);
    
    GLD(priv)->glBindProgramNV(GL_VERTEX_PROGRAM_NV, name);
    if (!priv->isVertexProgramEnabled) {
      glEnable(GL_VERTEX_PROGRAM_NV);
      priv->isVertexProgramEnabled = TRUE;
    }
  }
  CHECK_GL_ERROR("VP_enable");
}

/* GL_VP_disable
 * Disable vertex programs
 */
void 
GL_VP_disable(GL_D3D_priv* priv)
{
  if (priv->isVertexProgramEnabled) {
    if( HAVE_GL_EXT(priv, ARB_vertex_program) ) {
      TRACE("ARB: disabling vertex program\n");
      glDisable(GL_VERTEX_PROGRAM_ARB);
      priv->isVertexProgramEnabled = FALSE;
    } else if( HAVE_GL_EXT(priv, NV_vertex_program) ) {
      TRACE("NV: disabling vertex program\n");
      glDisable(GL_VERTEX_PROGRAM_NV);
      priv->isVertexProgramEnabled = FALSE;
    }
  }
  CHECK_GL_ERROR("VP_disable");
}

/* GL_VP_delete
 * Delete the vertex program identified by 'name'.
 */
static void 
GL_VP_delete(GL_D3D_priv *priv, GLuint name) 
{
  if( HAVE_GL_EXT(priv, ARB_vertex_program) ) {
    TRACE("ARB: deleting vertex program name=%u\n", name);
    
    GLD(priv)->glDeleteProgramsARB(1, &name);
  } else if(HAVE_GL_EXT(priv, NV_vertex_program) ) {
    TRACE("NV: deleting vertex program name=%u\n", name);
    
    GLD(priv)->glDeleteProgramsNV(1, &name);      
  }
  CHECK_GL_ERROR("VP_delete");
}


/* ===============  Externally called functions ========================== */

static HRESULT GL_init_vertex_shader_func(GL_D3D_priv* priv, VertexShader* vs)
{
  CONST DWORD *func = vs->pFunction;
  DWORD opcode, num_params, dst;
  HRESULT hr;
  TRACE("parsing vertex shader function block\n");

  if (!vs->FunctionLength) {
    TRACE("block is empty\n");
    return D3D_OK;
  }

  /* determine if the fog or point size output registers are used */
  func++; /* skip the version header */
  vs->has_fog = FALSE;
  vs->has_pointsize = FALSE;
  while( *func != D3DVS_END() ) {
  if( is_recognized_opcode(*func) ) {
    opcode = *func & D3DSI_OPCODE_MASK;
    num_params = num_opcode_operands(opcode);
    dst = func[1];
    /* check if instruction dest arg is a RASTOUT register */
    if( num_params > 0 && GETREGTYPE(dst) == D3DSPR_RASTOUT ) {
      switch( GETREGNUM(dst) ) {
	case D3DSRO_POSITION:
	  break;
	case D3DSRO_FOG:  /* instruction writes to fog output register */
	  vs->has_fog = TRUE;
	  break;
	case D3DSRO_POINT_SIZE: /* instruction writes to point size output register */
	  vs->has_pointsize = TRUE;
	  break;
	default:
	  break;
	}
      }
      func += num_params+1;
    } else if( is_comment(*func) ) {
      func += skip_comment(func);
    } else if( *func == D3DSIO_DEF ) {
      func += 6;
    } else if( *func == D3DSIO_DCL ) {
      func += 3;
    } else {
      func += dump_unrecognised_instruction(func);
    }
  }
  TRACE("has_fog=%d, has_pointsize=%d\n", vs->has_fog, vs->has_pointsize);
  hr = GL_VP_create(priv, vs);
  if (FAILED(hr)) return hr;

  /* preload the program(s) in the current context */
  /* this is not necessary, it will be done in setup_programmable
   * if it's not done here, but loading a new vertex program can be
   * slow so it's probably best to do it early */
  if (priv->callbacks.D3dRenderLock)
    priv->callbacks.D3dRenderLock(priv->d3dp->dwhContext, FALSE, NULL);
  GL_PrepProgram(priv, vs);
  if (priv->callbacks.D3dRenderUnlock)
    priv->callbacks.D3dRenderUnlock(priv->d3dp->dwhContext, FALSE);
  return hr;
}

static void GL_init_vertex_shader_input(VertexShader* vs, BYTE reg)
{
  switch (GET_INPUT_REG_TYPE(vs->input_reg[reg].type)) {
  case D3DVSDT_FLOAT1:
    vs->input_reg[reg].gltype = GL_FLOAT; 
    vs->input_reg[reg].size = 1; 
    vs->input_reg[reg].normalize = GL_FALSE; 
    break;
  case D3DVSDT_FLOAT2:
    vs->input_reg[reg].gltype = GL_FLOAT; 
    vs->input_reg[reg].size = 2; 
    vs->input_reg[reg].normalize = GL_FALSE; 
    break;
  case D3DVSDT_FLOAT3:
    vs->input_reg[reg].gltype = GL_FLOAT; 
    vs->input_reg[reg].size = 3; 
    vs->input_reg[reg].normalize = GL_FALSE; 
    break;
  case D3DVSDT_FLOAT4:
    vs->input_reg[reg].gltype = GL_FLOAT; 
    vs->input_reg[reg].size = 4; 
    vs->input_reg[reg].normalize = GL_FALSE; 
    break;
  case D3DVSDT_D3DCOLOR:
    vs->input_reg[reg].gltype = GL_UNSIGNED_BYTE; 
    vs->input_reg[reg].size = 4; 
    vs->input_reg[reg].normalize = GL_TRUE; 
    break;
  case D3DVSDT_UBYTE4:
    vs->input_reg[reg].gltype = GL_UNSIGNED_BYTE; 
    vs->input_reg[reg].size = 4; 
    vs->input_reg[reg].normalize = GL_TRUE; 
    break;
  case D3DVSDT_SHORT2:
    vs->input_reg[reg].gltype = GL_SHORT; 
    vs->input_reg[reg].size = 2; 
    vs->input_reg[reg].normalize = GL_FALSE; 
    break;
  case D3DVSDT_SHORT4:
    vs->input_reg[reg].gltype = GL_SHORT; 
    vs->input_reg[reg].size = 4; 
    vs->input_reg[reg].normalize = GL_FALSE; 
    break;
  default: 
    FIXME("unknown input register data type %d\n", GET_INPUT_REG_TYPE(vs->input_reg[reg].type));
  }
  TRACE("input_reg[%d]=[str(%d),off(%d),type(%s),gltype(%d),size(%d),norm(%d)]\n", 
        reg, vs->input_reg[reg].stream, vs->input_reg[reg].offset, 
        get_datatype(GET_INPUT_REG_TYPE(vs->input_reg[reg].type)),
        vs->input_reg[reg].gltype, vs->input_reg[reg].size,
        vs->input_reg[reg].normalize);
}

/* D3D_GL_create_vertex_shader
 * Handle dp2 command to create a vertex shader from the provided function 
 * and declaration blocks.  If the provided vertex shader is programmable, 
 * a corresponding vertex program implemementation is created for it. 
 */ 
HRESULT D3D_GL_create_vertex_shader(GL_D3D_priv* priv, 
				    LPD3DHAL_DP2COMMAND cmd, 
				    LPVOID data1, 
				    LPVOID data2)
{
  D3DHAL_DP2CREATEVERTEXSHADER *dat = (D3DHAL_DP2CREATEVERTEXSHADER *)data1;
  VertexShader *vs = VS_FROM_HANDLE(dat->dwHandle);
  
  TRACE("Create Vertex Shader handle=0x%lx\n", dat->dwHandle);

  if( IS_VS(dat->dwHandle) ) {

    /* handle the declaration block */
    if( vs->pDeclaration ) {
      CONST DWORD *decl = vs->pDeclaration;

      if( TRACE_ON(ddraw) ) {
        CONST DWORD *ptr = vs->pDeclaration;
        while (*ptr != D3DVSD_END()) {
          ptr += dump_vs_declaration(ptr);
        }
      }
      
      TRACE("parsing vertex shader declaration block\n");
      /* get the stream defs from the declaration block */
      while( (decl=find_vs_stream(decl)) != NULL ) {
	if( ISSTREAMTESS(*decl) ) {
	  FIXME("Stream(TESS) is not currently implemented\n");
	  decl++;
	} else {
	  BOOL done = FALSE;
	  BYTE stream = GETSTREAMNUM(*decl);
	  BYTE offset = 0;
	  
	  decl++;
	  do {
	    switch (GETTOKENTYPE(*decl)) {
	    case D3DVSD_TOKEN_TESSELLATOR:
	      FIXME("Tesselator data -- not currently implemented\n");
	      /* intentional fall-through */
	    case D3DVSD_TOKEN_NOP:
	      decl++;
	      break;
	    case D3DVSD_TOKEN_STREAMDATA:
	      if( ISSTREAMDATASKIP(*decl) ) {
		offset += GETSKIPCOUNT(*decl)*sizeof(DWORD);
	      } else {
		BYTE reg = GETVERTEXREG(*decl);
		BYTE type = GETDATATYPE(*decl);
		vs->input_reg[reg].stream = stream;
		vs->input_reg[reg].offset = offset;
		vs->input_reg[reg].type = SET_INPUT_REG_TYPE(type);
		GL_init_vertex_shader_input(vs, reg);
		offset += get_datatype_dimensionality(type)*sizeof(DWORD);
	      }
	      decl++;
	      break;
	    default:
	      done = TRUE;
	      break;
	    }
	  } while (!done);
	}  
      }  
    } /* end declaration block */
    
    /* handle the function block */
    if( vs->pFunction )
      return GL_init_vertex_shader_func(priv, vs);
  }

  return D3D_OK;
}

/* D3D_GL_create_vertex_shader_decl
 */ 
HRESULT D3D_GL_create_vertex_shader_decl(GL_D3D_priv* priv, 
					 LPD3DHAL_DP2COMMAND cmd, 
					 LPVOID data1, 
					 LPVOID data2)
{
  D3DHAL_DP2CREATEVERTEXSHADERDECL *dat = (D3DHAL_DP2CREATEVERTEXSHADERDECL *)data1;
  
  TRACE("Create Vertex Shader Declaration handle=0x%lx\n", dat->dwHandle);

  return D3D_OK;
}

/* D3D_GL_create_vertex_shader_func
 */ 
HRESULT D3D_GL_create_vertex_shader_func(GL_D3D_priv* priv, 
					 LPD3DHAL_DP2COMMAND cmd, 
					 LPVOID data1, 
					 LPVOID data2)
{
  D3DHAL_DP2CREATEVERTEXSHADERFUNC *dat = (D3DHAL_DP2CREATEVERTEXSHADERFUNC *)data1;
  VertexShader *vs = (VertexShader *)dat->dwHandle;
  
  TRACE("Create Vertex Shader Function handle=0x%lx\n", dat->dwHandle);

  if ( dat->dwHandle ) {
    if( vs->pFunction )
      return GL_init_vertex_shader_func(priv, vs);
  }

  return D3D_OK;
}

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

static HRESULT GL_init_vertex_shader_decl(GL_D3D_priv* priv, VertexShader* vs, DWORD vsd)
{
  CONST D3DVERTEXELEMENT9* pDecl = (D3DVERTEXELEMENT9*)vsd;
  CONST D3DVERTEXELEMENT9* vptr;
  CONST DWORD* program = vs->pFunction;
  CONST DWORD* ptr;
  DWORD tmplen;
  WORD last_stream;

  memset(vs->input_reg, 0, sizeof(vs->input_reg));

  /* match pDecl with DCLs */
  last_stream = 0xff;
  for (vptr = pDecl; 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) {
      last_stream = vptr->Stream;
      TRACE(" stream %d\n", last_stream);
    }
    /* 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) {
      DWORD reg = ptr[2] & D3DSP_REGNUM_MASK;
      TRACE("  matched usage %d index %d type %d with register v%ld\n", vptr->Usage, vptr->UsageIndex, vptr->Type, reg);
      vs->input_reg[reg].stream = vptr->Stream;
      vs->input_reg[reg].offset = vptr->Offset;
      vs->input_reg[reg].type = SET_INPUT_REG_TYPE(vptr->Type);
      GL_init_vertex_shader_input(vs, reg);
    } else {
      TRACE("  matched usage %d index %d type %d with nothing\n", vptr->Usage, vptr->UsageIndex, vptr->Type);
    }
  }

  return D3D_OK;
}

/* D3D_GL_set_current_vertex_shader
 * If the vertex shader is programmable, the declaration block is applied
 * and the corresponding vertex program is enabled and made current.
 * Otherwise, vertex programs are disabled.  
 */
HRESULT D3D_GL_set_current_vertex_shader(GL_D3D_priv* priv, DWORD dwHandle)
{
  VertexShader *vs = VS_FROM_HANDLE(dwHandle);

  priv->glFlags &= ~(GLF_VERTEX_DECLARATION|GLF_VERTEX_PROGRAM);
  if( IS_VS(dwHandle) ) {
    priv->current_vs = vs;

    if( vs->pDeclaration ) {
      CONST DWORD* decl = vs->pDeclaration; 
      TRACE("setting vertex shader declaration\n");
      priv->glFlags |= GLF_VERTEX_DECLARATION;

      while( (decl=find_vs_constmem(decl)) != NULL ) {
	unsigned count = GETCONSTCOUNT(*decl);
	unsigned addr = GETCONSTADDR(*decl);
	unsigned u;

	decl++;
	memcpy(priv->state.vertex_const + 4*addr, (float *)decl, 4*sizeof(float)*count);
	for (u=0; u<count; u++)
	  SET_DIRTY_VSC(&priv->state, addr+u);

	if (priv->d3dp->vertex_const)
	  memcpy(priv->d3dp->vertex_const + 4*addr, (float *)decl, 4*sizeof(float)*count);

	decl += 4*count;
      }
    }

    if( vs->pFunction ) {
      priv->glFlags |= GLF_VERTEX_PROGRAM;
    }

  } else {
    priv->current_vs = NULL;
  }

  priv->state.vertex_shader = dwHandle;
  priv->state.dirty_vs = TRUE;
  priv->glFlags |= GLF_NEW_TNL_MODE;

  return D3D_OK;
}

/* D3D_GL_set_current_vertex_shader_decl
 */
HRESULT D3D_GL_set_current_vertex_shader_decl(GL_D3D_priv* priv, DWORD dwHandle)
{
  priv->glFlags &= ~GLF_VERTEX_DECLARATION;
  if( dwHandle ) {
    priv->glFlags |= GLF_VERTEX_DECLARATION;
  }
  priv->state.vertex_decl = dwHandle;
  priv->state.dirty_vsd = TRUE;
  priv->glFlags |= GLF_NEW_TNL_MODE;

  return D3D_OK;
}

/* D3D_GL_set_current_vertex_shader_func
 */
HRESULT D3D_GL_set_current_vertex_shader_func(GL_D3D_priv* priv, DWORD dwHandle)
{
  VertexShader *vs = (VertexShader *)dwHandle;

  priv->glFlags &= ~GLF_VERTEX_PROGRAM;
  if( dwHandle ) {
    CONST DWORD* program = vs->pFunction;
    CONST DWORD* ptr;
    DWORD tmplen;

    priv->glFlags |= GLF_VERTEX_PROGRAM;
    priv->current_vs = vs;

    ptr = program+1;
    do {
      if (ptr[0] == D3DSIO_DEF) {
	DWORD addr = ptr[1] & D3DSP_REGNUM_MASK;
	memcpy(priv->state.vertex_const + 4*addr, (float *)(ptr+2), 4*sizeof(float));
	SET_DIRTY_VSC(&priv->state, addr);
	if (priv->d3dp->vertex_const)
	  memcpy(priv->d3dp->vertex_const + 4*addr, (float *)(ptr+2), 4*sizeof(float));
      }
      tmplen = hdr_op_len(ptr);
      ptr += tmplen;
    } while (tmplen);
  }
  priv->state.vertex_shader = dwHandle;
  priv->state.dirty_vs = TRUE;
  priv->glFlags |= GLF_NEW_TNL_MODE;

  return D3D_OK;
}

/* D3D_GL_set_vertex_shader
 * Handle dp2 command to set the current vertex shader.
 */
HRESULT D3D_GL_set_vertex_shader(GL_D3D_priv* priv, 
				 LPD3DHAL_DP2COMMAND cmd, 
				 LPVOID data1, 
				 LPVOID data2)
{
  D3DHAL_DP2VERTEXSHADER *dat = (D3DHAL_DP2VERTEXSHADER *)data1;
  TRACE("(%p)->(0x%lx)\n", priv, dat->dwHandle);

  if (GL_IN_BLOCK(priv)) {
    priv->block->vertex_shader = dat->dwHandle;
    priv->block->dirty_vs = TRUE;
  } else {
    D3D_GL_set_current_vertex_shader(priv, dat->dwHandle);
  }
  return D3D_OK;
}

/* D3D_GL_set_vertex_shader_decl
 */
HRESULT D3D_GL_set_vertex_shader_decl(GL_D3D_priv* priv, 
				      LPD3DHAL_DP2COMMAND cmd, 
				      LPVOID data1, 
				      LPVOID data2)
{
  D3DHAL_DP2VERTEXSHADER *dat = (D3DHAL_DP2VERTEXSHADER *)data1;
  TRACE("(%p)->(0x%lx)\n", priv, dat->dwHandle);

  if (GL_IN_BLOCK(priv)) {
    priv->block->vertex_decl = dat->dwHandle;
    priv->block->dirty_vsd = TRUE;
  } else {
    D3D_GL_set_current_vertex_shader_decl(priv, dat->dwHandle);
  }

  return D3D_OK;
}

/* D3D_GL_set_vertex_shader_func
 */
HRESULT D3D_GL_set_vertex_shader_func(GL_D3D_priv* priv, 
				      LPD3DHAL_DP2COMMAND cmd, 
				      LPVOID data1, 
				      LPVOID data2)
{
  D3DHAL_DP2VERTEXSHADER *dat = (D3DHAL_DP2VERTEXSHADER *)data1;
  TRACE("(%p)->(0x%lx)\n", priv, dat->dwHandle);

  if (GL_IN_BLOCK(priv)) {
    priv->block->vertex_shader = dat->dwHandle;
    priv->block->dirty_vs = TRUE;
  } else {
    D3D_GL_set_current_vertex_shader_func(priv, dat->dwHandle);
  }

  return D3D_OK;
}

/* D3D_GL_set_vertex_shader_const
 * Handle dp2 command to set vertex shader constants.
 * Sets the (global) constant registers to the provided values.
 */
HRESULT D3D_GL_set_vertex_shader_const(GL_D3D_priv* priv, 
				       LPD3DHAL_DP2COMMAND cmd, 
				       LPVOID data1, 
				       LPVOID data2)
{
  D3DHAL_DP2SETVERTEXSHADERCONST *dat = (D3DHAL_DP2SETVERTEXSHADERCONST *)data1;
  unsigned u;
  
  TRACE("(%p)->(%ld,%p,%ld)\n", priv, dat->dwRegister, data2, dat->dwCount);

  memcpy(priv->block->vertex_const + 4*dat->dwRegister, data2, 4*sizeof(float)*dat->dwCount);
  for (u=0; u<dat->dwCount; u++)
    SET_DIRTY_VSC(priv->block, dat->dwRegister+u);

  if (!GL_IN_BLOCK(priv)) {
    priv->glFlags |= GLF_NEW_VSHADER_CONSTS;

    if (priv->d3dp->vertex_const)
      memcpy(priv->d3dp->vertex_const + 4*dat->dwRegister, data2, 4*sizeof(float)*dat->dwCount);
  }

  return D3D_OK;
}

static HRESULT GL_delete_vertex_shader(GL_D3D_priv* priv, VertexShader* vs)
{
  if( priv->current_vs == vs ) {
    priv->current_vs = NULL;
    priv->state.vertex_shader = 0;
    priv->glFlags &= ~(GLF_VERTEX_DECLARATION|GLF_VERTEX_PROGRAM);
    priv->glFlags |= GLF_NEW_TNL_MODE;
    priv->state.dirty_vs = TRUE;
  }
    
  if( vs->pFunction && vs->DriverReserved1 ) {
    if (priv->callbacks.D3dRenderLock)
      priv->callbacks.D3dRenderLock(priv->d3dp->dwhContext, FALSE, NULL);
    GL_VP_delete(priv, (GLuint)vs->DriverReserved0);
    if (priv->callbacks.D3dRenderUnlock)
      priv->callbacks.D3dRenderUnlock(priv->d3dp->dwhContext, FALSE);
    vs->DriverReserved1 = 0;
  }
  if( vs->programstring[0]) {
    HeapFree(GetProcessHeap(), 0, vs->programstring[0]);
  }
  if( vs->programstring[1]) {
    HeapFree(GetProcessHeap(), 0, vs->programstring[1]);
  }
  return D3D_OK;
}

/* D3D_GL_delete_vertex_shader
 * Handle dp2 command to delete the specified vertex shader.
 * If the vertex shader is programmable, all resources that were previously
 * allocated for it are deleted. 
 */
HRESULT D3D_GL_delete_vertex_shader(GL_D3D_priv* priv, 
				    LPD3DHAL_DP2COMMAND cmd, 
				    LPVOID data1, 
				    LPVOID data2)
{
  D3DHAL_DP2VERTEXSHADER *dat = (D3DHAL_DP2VERTEXSHADER *)data1;
  VertexShader *vs = VS_FROM_HANDLE(dat->dwHandle);
  TRACE("priv(%p)->dwHandle(0x%lx)\n", priv, dat->dwHandle);
  
  if( IS_VS(dat->dwHandle) ) {
    GL_delete_vertex_shader(priv, vs);
  }
  return D3D_OK;
}

/* D3D_GL_delete_vertex_shader_decl
 */
HRESULT D3D_GL_delete_vertex_shader_decl(GL_D3D_priv* priv, 
					 LPD3DHAL_DP2COMMAND cmd, 
					 LPVOID data1, 
					 LPVOID data2)
{
  D3DHAL_DP2VERTEXSHADER *dat = (D3DHAL_DP2VERTEXSHADER *)data1;
  TRACE("priv(%p)->dwHandle(0x%lx)\n", priv, dat->dwHandle);

  if (priv->state.vertex_decl == dat->dwHandle) {
    priv->state.vertex_decl = 0;
    priv->glFlags &= ~GLF_VERTEX_DECLARATION;
    priv->glFlags |= GLF_NEW_TNL_MODE;
    priv->state.dirty_vsd = TRUE;
  }

  return D3D_OK;
}

/* D3D_GL_delete_vertex_shader_func
 */
HRESULT D3D_GL_delete_vertex_shader_func(GL_D3D_priv* priv, 
					 LPD3DHAL_DP2COMMAND cmd, 
					 LPVOID data1, 
					 LPVOID data2)
{
  D3DHAL_DP2VERTEXSHADER *dat = (D3DHAL_DP2VERTEXSHADER *)data1;
  VertexShader *vs = (VertexShader *)dat->dwHandle;
  TRACE("priv(%p)->dwHandle(0x%lx)\n", priv, dat->dwHandle);

  if( dat->dwHandle ) {
    GL_delete_vertex_shader(priv, vs);
  }
  return D3D_OK;
}

#ifdef DEBUG_VERTEX_DATA
void dump_vertex(GL_D3D_priv* priv, DWORD dwIndex)
{
  VertexShader *vs = priv->current_vs;
  int i;
  D3DVALUE *ptr;
  DWORD *dptr;
  WORD *sptr;
  
  for( i=0; i<MAX_VERTEX_ATTRIBUTES; i++ ) {
    if( IS_INPUT_REG_ENABLED(vs->input_reg[i].type) ) {
      BYTE streamnum = vs->input_reg[i].stream;
      GLuint stride;
      const GLvoid *pointer;
      if (priv->glFlags & GLF_VERTEX_PROGRAM) {
	if (streamnum || (priv->glFlags & GLF_VERTEX_BUFFER)) {
	  /* normal case */
	  stride = priv->d3dp->stream[streamnum].stride;
	  pointer = (LPVOID)priv->d3dp->stream[streamnum].data_t->gbl.fpVidMem;
	}
	else if (priv->glFlags & GLF_STRIDED_DATA) {
	  /* stream zero user pointer (Draw[Indexed]PrimitiveUP) */
	  stride = priv->sd.position.dwStride;
	  pointer = priv->sd.position.lpvData;
	}
	else {
	  FIXME("vertex program stream zero badness\n");
	  stride = priv->d3dp->stream[0].stride;
	  pointer = (LPVOID)priv->d3dp->stream[0].data_t->gbl.fpVidMem;
	}
      }
      else {
	/* this is a vertex shader from fixedprogram */
	D3DDP_PTRSTRIDE* ptrstrides = &priv->stride.position;
	stride = ptrstrides[streamnum].dwStride;
	pointer = ptrstrides[streamnum].lpvData;
      }
      pointer += vs->input_reg[i].offset + (dwIndex * stride);
      ptr = (D3DVALUE *)pointer;
      sptr = (WORD *)pointer;
      dptr = (DWORD *)pointer;

      switch (GET_INPUT_REG_TYPE(vs->input_reg[i].type)) {
      case D3DVSDT_FLOAT1:
	TRACE("  reg %02d: %f\n", i, ptr[0]); break;
      case D3DVSDT_FLOAT2:
	TRACE("  reg %02d: %f, %f\n", i, ptr[0], ptr[1]); break;
      case D3DVSDT_FLOAT3:
	TRACE("  reg %02d: %f, %f, %f\n", i, ptr[0], ptr[1], ptr[2]); break;
      case D3DVSDT_FLOAT4:
	TRACE("  reg %02d: %f, %f, %f, %f\n", i, ptr[0], ptr[1], ptr[2], ptr[3]); break;
      case D3DVSDT_D3DCOLOR:
	TRACE("  reg %02d: %08lx\n", i, dptr[0]); break;
      case D3DVSDT_UBYTE4:
	TRACE("  reg %02d: %08lx\n", i, dptr[0]); break;
      case D3DVSDT_SHORT2:
	TRACE("  reg %02d: %d, %d\n", i, sptr[0], sptr[1]); break;
      case D3DVSDT_SHORT4:
	TRACE("  reg %02d: %d, %d, %d, %d\n", i, sptr[0], sptr[1], sptr[2], sptr[3]); break;
      }
    }
  }
}
#endif

/* D3D_GL_setup_programmable
 * Do any pre-rendering setup that needs to be done for vertex programs.
 * This disables the conventional arrays and sets up the vertex attribute pointers.
 */
void D3D_GL_setup_programmable(GL_D3D_priv* priv, LPVOID data1,
                               const WORD* indices, GLenum pt, DWORD dwCount, BOOL indexed)
{
  VertexShader *vs = priv->current_vs;
  GLuint i;
  DWORD dwIndexBase; 
  DWORD dwVStart, dwVCount;
  BOOL inverted = (priv->glFlags & GLF_RENDER_INVERTED) == GLF_RENDER_INVERTED;
  BOOL do_conversion = (priv->glFlags & GLF_VERTEX_BUFFER) == GLF_VERTEX_BUFFER;

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

  TRACE("setup programmable (%p)\n", vs);

  /* disable the conventional arrays */
  if( priv->pVertexArray ) {
    glDisableClientState(GL_VERTEX_ARRAY);
    priv->pVertexArray = NULL;
    priv->sVertexArray = 0;
  }
  if( priv->pNormalArray ) {
    glDisableClientState(GL_NORMAL_ARRAY);
    priv->pNormalArray = NULL;
    priv->sNormalArray = 0;
  }
  if( priv->pColorArray ) {
    glDisableClientState(GL_COLOR_ARRAY);
    priv->pColorArray = NULL;
    priv->sColorArray = 0;
  }
  if( HAVE_GL_EXT(priv, EXT_secondary_color) && priv->pSecondaryColorArray ) {
    glDisableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
    priv->pSecondaryColorArray = NULL;
    priv->sSecondaryColorArray = 0;
  }
  if( HAVE_GL_EXT(priv, EXT_fog_coord) && priv->pFogCoordArray ) {
    glDisableClientState(GL_FOG_COORDINATE_ARRAY_EXT);
    priv->pFogCoordArray = NULL;
    priv->sFogCoordArray = 0;
  }
  for( i=0; i<priv->lpCaps->wMaxTextureBlendStages; i++ ) {
    if( priv->pTexCoordArray[i] ) {
      GLD(priv)->glClientActiveTextureARB(GL_TEXTURE0_ARB + i);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
      priv->pTexCoordArray[i] = NULL;
      priv->sTexCoordArray[i] = 0;
    }
  }
  if( HAVE_GL_EXT(priv, ARB_vertex_blend) ) {
    glDisableClientState(GL_WEIGHT_ARRAY_ARB);
  } else if( HAVE_GL_EXT(priv, EXT_vertex_weighting) ) {
    glDisableClientState(GL_VERTEX_WEIGHT_ARRAY_EXT);
  }

  /* setup fog state if necessary */
  GL_setup_linear_fog(priv, TRUE, (vs->has_fog && priv->state.renderstate[D3DRS_FOGENABLE]));

  if( vs->has_pointsize ) {
    FIXME("vertex program uses point size - not implemented yet\n");
  }

  /* check if vertex declaration must be parsed
   * (FIXME: could probably optimize this some more) */
  if( priv->state.dirty_vsd || priv->state.dirty_vs ) {
    if( vs && vs->pFunction && !vs->pDeclaration ) {
      TRACE("updating vertex shader declaration\n");
      GL_init_vertex_shader_decl(priv, vs, priv->state.vertex_decl);
    }
    priv->state.dirty_vsd = FALSE;
  }

  /* check if vertex program must be loaded */
  if( priv->state.dirty_vs ) {
    if( vs->pFunction ) {
      /* if vertex program doesn't belong to a context, then add it */
      if( !vs->DriverReserved1 ) {
        GL_PrepProgram(priv, vs);
      }
      TRACE("setting vertex shader function\n");
      GL_VP_enable(priv, (GLuint)vs->DriverReserved0);
    } else {
      /* shouldn't happen */
      GL_VP_disable(priv);
    }
    priv->state.dirty_vs = FALSE;
  }

  /* check if rendering needs to be inverted (render-to-texture) */
  if( inverted != vs->inverted ) {
    GL_LoadProgram(priv, vs, inverted);
  }

  /* check for vertex constants that must be loaded */
  if( priv->glFlags & GLF_NEW_VSHADER_CONSTS ) {
    unsigned m, c;
    DWORD b;
    for (m=0; m<(96/32); m++) if (priv->state.dirty_vsc[m]) {
      c = m * 32;
      b = 1;
      while (b) {
        if (priv->state.dirty_vsc[m] & b) {
          TRACE("loading constant %d (%1.8f, %1.8f, %1.8f, %1.8f)\n", c,
            priv->state.vertex_const[4*c+0],
            priv->state.vertex_const[4*c+1],
            priv->state.vertex_const[4*c+2],
            priv->state.vertex_const[4*c+3]);
          GL_VP_set_constant(priv, c, 1, priv->state.vertex_const + 4*c);
        }
        c++;
        b <<= 1;
      }
      priv->state.dirty_vsc[m] = 0;
    }
    priv->glFlags &= ~GLF_NEW_VSHADER_CONSTS;
  }

  /* setup the vertex attribute arrays */
  for( i=0; i<MAX_VERTEX_ATTRIBUTES; i++ ) {

    if( IS_INPUT_REG_ENABLED(vs->input_reg[i].type) ) {
      BYTE streamnum = vs->input_reg[i].stream;
      GLuint stride;
      const GLvoid *pointer;
      if (priv->glFlags & GLF_VERTEX_PROGRAM) {
	if (streamnum || (priv->glFlags & GLF_VERTEX_BUFFER)) {
	  /* normal case */
	  stride = priv->d3dp->stream[streamnum].stride;
	  if (priv->callbacks.D3dVbPreRender)
	    pointer = priv->callbacks.D3dVbPreRender(
		       &priv->d3dp->stream[streamnum].data_t->lcl,
		       NULL, do_conversion, 0, 0, NULL, 0, 0);
	  else
	    pointer = (LPVOID)priv->d3dp->stream[streamnum].data_t->gbl.fpVidMem;
	}
	else if (priv->glFlags & GLF_STRIDED_DATA) {
	  /* stream zero user pointer (Draw[Indexed]PrimitiveUP) */
	  stride = priv->sd.position.dwStride;
	  pointer = priv->sd.position.lpvData;
	}
	else {
	  FIXME("vertex program stream zero badness\n");
	  stride = priv->d3dp->stream[0].stride;
	  pointer = (LPVOID)priv->d3dp->stream[0].data_t->gbl.fpVidMem;
	}
      }
      else {
	/* this is a vertex shader from fixedprogram */
	D3DDP_PTRSTRIDE* ptrstrides = &priv->stride.position;
	stride = ptrstrides[streamnum].dwStride;
	pointer = ptrstrides[streamnum].lpvData;
      }
      pointer += vs->input_reg[i].offset + (dwIndexBase * stride);

      /* TRACE("input reg%d: %d, 0x%02x, %d, %d, %ld, %p\n", i, streamnum,
	*    GET_INPUT_REG_TYPE(vs->input_reg[i].type),
	*    vs->input_reg[i].offset, stride, dwIndexBase,
	*    pointer); */

      if( HAVE_GL_EXT(priv, ARB_vertex_program) ) {
	/* TRACE("ARB: enabling and setting vertex attrib pointer (%d,%d,%d,%d,%d,%p)\n",
	 *     i, vs->input_reg[i].size, vs->input_reg[i].gltype, 
	 *     vs->input_reg[i].normalize, stride, pointer);  */
	GLD(priv)->glEnableVertexAttribArrayARB(i);
	GLD(priv)->glVertexAttribPointerARB(i, vs->input_reg[i].size, vs->input_reg[i].gltype, 
	                                    vs->input_reg[i].normalize, stride, pointer);
      } else if( HAVE_GL_EXT(priv, NV_vertex_program) ) {
	/* TRACE("NV: enabling and setting vertex attrib pointer (%d,%d,%d,%d,%p)\n",
	 *     i, vs->input_reg[i].size, vs->input_reg[i].gltype, stride, pointer); */
	glEnableClientState(GL_VERTEX_ATTRIB_ARRAY0_NV+i);
	GLD(priv)->glVertexAttribPointerNV(i, vs->input_reg[i].size, vs->input_reg[i].gltype, 
	                                   stride, pointer);
      }
    }
  }

  TRACE("ibase=%ld, vstart=%ld, vcount=%ld\n", 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);
	dump_vertex(priv, 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++) {
	dump_vertex(priv, dwVStart + dat->wVStart + i);
      }
    }
#endif
  }

  CHECK_GL_ERROR("setup programmable");
}

/* D3D_GL_finish_programmable
 * Do any post-rendering cleanup that needs to be done.
 * Disables all vertex attribute arrays, so that fixed function rendering
 * can take place normally.
 */
void D3D_GL_finish_programmable(GL_D3D_priv* priv) 
{
  VertexShader *vs = priv->current_vs;
  GLuint i;

  TRACE("disabling all vertex attribute arrays\n");
  for( i=0; i<MAX_VERTEX_ATTRIBUTES; i++ ) {

    if( IS_INPUT_REG_ENABLED(vs->input_reg[i].type) ) {
      BYTE streamnum = vs->input_reg[i].stream;
      if (priv->glFlags & GLF_VERTEX_PROGRAM) {
	if (streamnum || (priv->glFlags & GLF_VERTEX_BUFFER)) {
	  /* normal case */
	  if (priv->callbacks.D3dVbPostRender)
	    priv->callbacks.D3dVbPostRender(
	      &priv->d3dp->stream[streamnum].data_t->lcl);
	}
      }
    }

    if( HAVE_GL_EXT(priv, ARB_vertex_program) ) {
      GLD(priv)->glDisableVertexAttribArrayARB(i);
    } else if( HAVE_GL_EXT(priv, NV_vertex_program) ) {
      glDisableClientState(GL_VERTEX_ATTRIB_ARRAY0_NV + i);
    }
  }
  CHECK_GL_ERROR("finish programmable");
}

/* D3D_GL_VertexProgram_Init
 * Called on startup. Auto-detects available vertex program extensions
 * and sets up various function pointers accordingly.
 * Also probes driver for any information that it can obtain about
 * various capabilities.
 */
void D3D_GL_VertexProgram_Init(DD_GL_data *gld)
{
  /* hack to disable ARB vertex programs - for testing NV */
  /* gld->gl_ext[ARB_vertex_program] = 0; */

  /* configure the appropriate function pointers */
  if( gld->gl_ext[ARB_vertex_program] ) {
    TRACE("setting up ARB vertex programs\n");
    dump_opcode = dump_opcode_arb;
    dump_register = dump_register_arb;
    GL_PrepProgram = GL_PrepProgram_arb;
    GL_LoadProgram = GL_LoadProgram_arb;
  } else if( gld->gl_ext[NV_vertex_program] || gld->gl_ext[NV_vertex_program1_1] ) {
    TRACE("setting up NV vertex programs\n");
    dump_opcode = dump_opcode_nv;
    dump_register = dump_register_nv;
    GL_PrepProgram = GL_PrepProgram_nv;
    GL_LoadProgram = GL_LoadProgram_nv;
  } else {
    TRACE("no usable vertex programs found\n");
  }
  
  if( gld->gl_ext[ARB_vertex_program] && TRACE_ON(ddraw) ) {
    GLint param;
    TRACE("Probing driver for ARB vertex program capabilities\n");

    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_ENV_PARAMETERS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_INSTRUCTIONS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_TEMPORARIES_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_PARAMETERS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_ATTRIBS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_ATTRIBS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB = %d\n", param);
    gld->glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB, &param);
    TRACE("\tGL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB = %d\n", param);
  }
}
