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

#include "config.h"

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

#include "d3d9_private.h"

#define WINEX_FPS_COUNTER

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

/* FPU control hack - prevents problems in some apps (ie: Sacrifice) that
   mess with the FPU state and don't restore it before calling D3D.  This
   hack simply resets the FPU control state while in a scene.  By default we
   have it on, but we may want to consider this further later.  FIXME! */
#ifdef DDRAW_SAVE_AND_RESTORE_FPU_HACK
#include "wine/d3d_fpuctl.h"
   static const fpu_control_t fpu_default = _FPU_DEFAULT;

   /* Reset the FPU state to default while doing a scene */
#  define SAVE_AND_CLEAN_FPU_STATE() \
     do { \
       _FPU_GETCW(This->fpucw); \
       _FPU_SETCW(fpu_default); \
     } while(0)

  /* Restore the FPU state now that the scene is over */
#  define RESTORE_FPU_STATE() \
     do { \
       _FPU_SETCW(This->fpucw); \
     } while(0)

#else
#  define SAVE_AND_CLEAN_FPU_STATE() do {} while(0)
#  define RESTORE_FPU_STATE() do {} while(0)
#endif /* DDRAW_SAVE_AND_RESTORE_FPU_HACK */

/* Very crude fps counter for 3d games - could use flip for 2d games */
#ifndef WINEX_FPS_COUNTER
#  define FPS_BEGIN_SCENE() do {} while(0)
#  define FPS_END_SCENE() do {} while(0)
#else
  WINE_DECLARE_DEBUG_CHANNEL(fps);

  static DWORD dwFirstFPSCounterTime = 0;
  static ULONG ulFPSCounterFrames = 0;
  static BOOL  bFPSCounterInitialized = FALSE;
  static const DWORD cdwTickInterval = 5*1000; /* Output at most every 5sec */
# define FPS_BEGIN_SCENE() \
     do { \
       if( !bFPSCounterInitialized ) { dwFirstFPSCounterTime = GetTickCount(); bFPSCounterInitialized = TRUE; } \
     } while(0)
# define FPS_END_SCENE() \
     do { \
       DWORD dwCurrentFPSCounterTime = GetTickCount(); \
       ulFPSCounterFrames++; \
\
       if( dwCurrentFPSCounterTime >= dwFirstFPSCounterTime + cdwTickInterval ) \
       { \
         TRACE_(fps)("%lu frames in %lu ms = %f fps\n", \
	               ulFPSCounterFrames, dwCurrentFPSCounterTime - dwFirstFPSCounterTime, \
		       (FLOAT)(ulFPSCounterFrames*1000)/(dwCurrentFPSCounterTime - dwFirstFPSCounterTime) ); \
         dwFirstFPSCounterTime = dwCurrentFPSCounterTime; \
	 ulFPSCounterFrames = 0; \
       } \
     } while(0) 
#endif /* WINEX_FPS_COUNTER */


#define R_X(x) (x->left)
#define R_Y(x) (x->top)
#define R_W(x) (x->right - x->left)
#define R_H(x) (x->bottom - x->top)

static void Direct3DDevice9_UpdateTextures(LPDIRECT3DDEVICE9 iface)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  unsigned u;
  This->dirty_textures = FALSE;
  for (u=0; u<8; u++)
    if (This->state.tex[u]) {
      IDirect3DBaseTexture9Impl* tex = (IDirect3DBaseTexture9Impl*)This->state.tex[u];
      tex->UpdateTexture(tex, NULL);
    }
}

#define UPDATE_TEXTURES if (This->dirty_textures) Direct3DDevice9_UpdateTextures(iface);

static void Direct3DDevice9_UpdateShader(LPDIRECT3DDEVICE9 iface)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  VertexShader* vs = NULL;
  DWORD fvf = 0;

  if (dx9_hal) return;

  if (This->state.vertex_shader) {
    Direct3DDevice9_BuildVertexShader(iface,
      (IDirect3DVertexDeclaration9Impl*)This->state.vertex_decl,
      (IDirect3DVertexShader9Impl*)This->state.vertex_shader,
      &vs);
    fvf = vs->FVF;
  }
  else if (This->state.vertex_decl) {
    Direct3DDevice9_BuildVertexShader(iface,
      (IDirect3DVertexDeclaration9Impl*)This->state.vertex_decl,
      NULL,
      &vs);
    fvf = vs->FVF;
  } else {
    TRACE("using FVF shader\n");
    fvf = This->d3dp.vertex_fvf;
  }
  if (This->d3dp.vertex_shader != vs) {
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADER, 0, {1} };
    D3DHAL_DP2VERTEXSHADER data;
    This->d3dp.vertex_shader = vs;
    data.dwHandle = vs ? VS_TO_HANDLE(vs) : 0;
    This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  }
  This->d3dp.vertex_fvf = fvf;
}

#define UPDATE_SHADER if (!dx9_hal) Direct3DDevice9_UpdateShader(iface);

HRESULT WINAPI Direct3DDevice9_Present(LPDIRECT3DDEVICE9 iface,
				       CONST RECT* pSourceRect,
				       CONST RECT* pDestRect,
				       HWND hDestWindowOverride,
				       CONST RGNDATA* pDirtyRegion)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  HWND hwnd;

  TRACE("(%p)->(%p,%p,%08x,%p)\n", iface,
	pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);

  if (!This->backbuf) return D3DERR_DEVICELOST;

  if (hDestWindowOverride) hwnd = hDestWindowOverride;
  else hwnd = This->local.hWnd;

  if (This->windowed) {
    RECT source, client;
    HDC dc;

    source.left = 0;
    source.top = 0;
    source.right = ((IDirect3DSurface9Impl*)This->backbuf)->t.gbl.wWidth;
    source.bottom = ((IDirect3DSurface9Impl*)This->backbuf)->t.gbl.wHeight;
    if (!pSourceRect) pSourceRect = &source;

    GetClientRect(hwnd, &client);
    if (!pDestRect) pDestRect = &client;

    dc = GetDC(hwnd);
    if ((R_W(pDestRect) == R_W(pSourceRect)) &&
	(R_H(pDestRect) == R_H(pSourceRect)))
      BitBlt(dc,
	     R_X(pDestRect), R_Y(pDestRect),
	     R_W(pDestRect), R_H(pDestRect),
	     This->mem_dc,
	     R_X(pSourceRect), R_Y(pSourceRect),
	     SRCCOPY);
    else
      StretchBlt(dc,
		 R_X(pDestRect), R_Y(pDestRect),
		 R_W(pDestRect), R_H(pDestRect),
		 This->mem_dc,
		 R_X(pSourceRect), R_Y(pSourceRect),
		 R_W(pSourceRect), R_H(pSourceRect),
		 SRCCOPY);
    ReleaseDC(hwnd, dc);
  }
  else {
    DDHAL_FLIPDATA data;
    HRESULT hr;
    IDirect3DSurface9Impl* front = (IDirect3DSurface9Impl*)This->primary;
    IDirect3DSurface9Impl* back = (IDirect3DSurface9Impl*)This->backbuf;

    {
	HBITMAP tmp = front->dib;
	front->dib = back->dib;
	back->dib = tmp;
    }
    {
	BOOL tmp = front->own_memory;
	front->own_memory = back->own_memory;
	back->own_memory = tmp;
    }
    {
	ULONG_PTR tmp = front->t.gbl.fpVidMem;
	front->t.gbl.fpVidMem = back->t.gbl.fpVidMem;
	back->t.gbl.fpVidMem = tmp;
    }
    {
	ULONG_PTR tmp = front->t.gbl_more.hKernelSurface;
	front->t.gbl_more.hKernelSurface = back->t.gbl_more.hKernelSurface;
	back->t.gbl_more.hKernelSurface = tmp;
    }

    /* FIXME: handle other swap effects than FLIP */
    data.lpDD = This->local.lpGbl;
    data.lpSurfCurr = &front->t.lcl;
    data.lpSurfTarg = &back->t.lcl;
    data.lpSurfCurrLeft = NULL;
    data.lpSurfTargLeft = NULL;
    data.dwFlags = 0; /* FIXME: fill flags by swap effect */
    data.ddRVal = 0;
    data.Flip = This->local.lpGbl->lpDDCBtmp->HALDDSurface.Flip;
    if (data.Flip)
      hr = data.Flip(&data);
  }
  return D3D_OK;
}

VOID WINAPI Direct3DDevice9_SetGammaRamp(LPDIRECT3DDEVICE9 iface,
					 UINT iSwapChain,
					 DWORD Flags,
					 CONST D3DGAMMARAMP* pRamp)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  HWND hwnd = This->local.hWnd;
  HDC dc;

  TRACE("(%p)->(%d,%ld,%p)\n", iface, iSwapChain, Flags, pRamp);

  if (iSwapChain) {
    FIXME("swapchain=%d\n", iSwapChain);
    return;
  }

  dc = GetDC(hwnd);
  SetDeviceGammaRamp(dc, (LPVOID)pRamp);
  ReleaseDC(hwnd, dc);
}

VOID WINAPI Direct3DDevice9_GetGammaRamp(LPDIRECT3DDEVICE9 iface,
					 UINT iSwapChain,
					 D3DGAMMARAMP* pRamp)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  HWND hwnd = This->local.hWnd;
  HDC dc;

  TRACE("(%p)->(%d,%p)\n", iface, iSwapChain, pRamp);

  if (iSwapChain) {
    FIXME("swapchain=%d\n", iSwapChain);
    return;
  }

  dc = GetDC(hwnd);
  if (!GetDeviceGammaRamp(dc, (LPVOID)pRamp))
    memcpy(pRamp, &This->orig_gamma, sizeof(D3DGAMMARAMP));
  ReleaseDC(hwnd, dc);
}

HRESULT WINAPI Direct3DDevice9_CopyRects(LPDIRECT3DDEVICE9 iface,
					 IDirect3DSurface9* pSourceSurface,
					 CONST RECT* pSourceRectsArray,
					 UINT cRects,
					 IDirect3DSurface9* pDestSurface,
					 CONST POINT* pDestPointsArray)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  IDirect3DSurface9Impl *src = (IDirect3DSurface9Impl*)pSourceSurface;
  IDirect3DSurface9Impl *dst = (IDirect3DSurface9Impl*)pDestSurface;
  LPDDPIXELFORMAT psrc, pdst;
  RECT srect;
  POINT dpoint;
  D3DLOCKED_RECT rsrc, rdst;
  IDirect3DBaseTexture9Impl* tex = NULL;
  LPBYTE sp, dp;
  UINT bpp, c;
  HRESULT hr = D3D_OK;

  TRACE("(%p)->(%p,%p,%d,%p,%p)\n", iface,
	pSourceSurface, pSourceRectsArray, cRects,
	pDestSurface, pDestPointsArray);

  psrc = &src->t.gbl.ddpfSurface;
  pdst = &dst->t.gbl.ddpfSurface;

  if (src->Format != dst->Format) {
    TRACE("attempted format conversion, not supported\n");
    return D3DERR_INVALIDCALL;
  }

  srect.top = 0;
  srect.left = 0;
  srect.right = src->t.gbl.wWidth;
  srect.bottom = src->t.gbl.wHeight;
  dpoint.x = 0;
  dpoint.y = 0;
  if (!cRects) {
    if ((srect.right != dst->t.gbl.wWidth) ||
	(srect.bottom != dst->t.gbl.wHeight))
      return D3DERR_INVALIDCALL;
    pSourceRectsArray = &srect;
    pDestPointsArray = &dpoint;
    cRects = 1;
  }

  if (!dst->t.gbl.fpVidMem) {
    /* the destination surface is controlled by the HAL,
     * tell it to perform the copy */
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXBLT, 0, {1} };
    D3DHAL_DP2TEXBLT data;
    data.dwDDSrcSurface = (DWORD)&src->t;
    data.dwDDDestSurface = (DWORD)&dst->t;
    data.dwFlags = 0;
    /* copy all of it for now */
    for (c=0; c<cRects; c++) {
      memcpy(&data.pDest, &pDestPointsArray[c], sizeof(POINT));
      memcpy(&data.rSrc, &pSourceRectsArray[c], sizeof(RECT));
      hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
      if (FAILED(hr)) break;
    }
    return hr;
  }

  /* get texture object if appropriate */
  if ((dst->t.lcl.ddsCaps.dwCaps & DDSCAPS_TEXTURE) &&
      !(dst->t.more.ddsCapsEx.dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL))
    IDirect3DSurface9_GetContainer(pDestSurface, &IID_IDirect3DBaseTexture9, (void**)&tex);

  bpp = (psrc->u1.dwRGBBitCount+7)/8;

  hr = IDirect3DSurface9_LockRect(pSourceSurface, &rsrc, NULL,
				  D3DLOCK_READONLY);
  if (FAILED(hr)) goto finito;
  hr = IDirect3DSurface9_LockRect(pDestSurface, &rdst, NULL,
				  D3DLOCK_NO_DIRTY_UPDATE);
  if (FAILED(hr)) {
    IDirect3DSurface9_UnlockRect(pSourceSurface);
    goto finito;
  }

  sp = rsrc.pBits;
  dp = rdst.pBits;

  /* CopyRects does not clip or anything */
  for (c=0; c<cRects; c++) {
    CONST RECT* crect = &pSourceRectsArray[c];
    CONST POINT* cpoint = &pDestPointsArray[c];
    LPBYTE sl = sp + rsrc.Pitch * crect->top + bpp * crect->left;
    LPBYTE dl = dp + rdst.Pitch * cpoint->y + bpp * cpoint->x;
    UINT ll = (crect->right - crect->left) * bpp;
    UINT lc = (crect->bottom - crect->top);

    while (lc) {
      memcpy(dl, sl, ll);
      sl += rsrc.Pitch;
      dl += rdst.Pitch;
      lc--;
    }

    /* update texture's dirty region */
    if (tex) {
      RECT rect;
      INT x = cpoint->x - crect->left;
      INT y = cpoint->y - crect->top;
      rect.left   = crect->left   + x;
      rect.top    = crect->top    + y;
      rect.right  = crect->right  + x;
      rect.bottom = crect->bottom + y;
      tex->AddDirtyRect(tex, pDestSurface, &rect);
    }
  }

  IDirect3DSurface9_UnlockRect(pDestSurface);
  IDirect3DSurface9_UnlockRect(pSourceSurface);

finito:
  if (tex) IDirect3DTexture9_Release((LPDIRECT3DTEXTURE9)tex);

  return hr;
}

HRESULT WINAPI Direct3DDevice9_UpdateTexture(LPDIRECT3DDEVICE9 iface,
                                             IDirect3DBaseTexture9* pSourceTexture,
                                             IDirect3DBaseTexture9* pDestinationTexture)
{
  IDirect3DBaseTexture9Impl* tex;
  D3DRESOURCETYPE st, dt;

  TRACE("(%p)->(%p,%p)\n", iface, pSourceTexture, pDestinationTexture);
  st = IDirect3DBaseTexture9_GetType(pSourceTexture);
  dt = IDirect3DBaseTexture9_GetType(pDestinationTexture);
  if (st != dt) return D3DERR_INVALIDCALL;
  tex = (IDirect3DBaseTexture9Impl*)pSourceTexture;
  return tex->UpdateTexture(tex, (IDirect3DBaseTexture9Impl*)pDestinationTexture);
}

HRESULT WINAPI Direct3DDevice9_BeginScene(LPDIRECT3DDEVICE9 iface)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_SCENECAPTUREDATA data;
  HRESULT hr;

  TRACE("(%p)\n", This);
  if (This->d3dp.in_scene) return D3DERR_INVALIDCALL;

  SAVE_AND_CLEAN_FPU_STATE();
  FPS_BEGIN_SCENE();

  /* initialize rendering, possibly locking rendering resources */
  This->d3dp.in_scene = TRUE;

  data.dwhContext = This->d3dp.dwhContext;
  data.dwFlag = D3DHAL_SCENE_CAPTURE_START;
  data.ddrval = 0;
  if (This->d3dp.cbs1->SceneCapture)
    This->d3dp.cbs1->SceneCapture(&data);
#if 0
  if (data.ddrval == D3D_OK)
    This->surface->lock_update(This->surface, NULL, 0);
#endif
  hr = data.ddrval;

  if (FAILED(hr)) {
    This->d3dp.in_scene = FALSE;
    return hr;
  }

  /* transmit any state changes that may have been stored
   * while the renderer was unlocked */
  This->flush_dp2(This->ip_ctx);

  UPDATE_TEXTURES

  return hr;
}

HRESULT WINAPI Direct3DDevice9_EndScene(LPDIRECT3DDEVICE9 iface)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_SCENECAPTUREDATA data;
  HRESULT hr;

  TRACE("(%p)\n", This);
  if (!This->d3dp.in_scene) return D3DERR_INVALIDCALL;

  /* make sure all the commands have reached the renderer */
  This->flush_dp2(This->ip_ctx);

  /* shutdown rendering, releasing any locked rendering resources */
  data.dwhContext = This->d3dp.dwhContext;
  data.dwFlag = D3DHAL_SCENE_CAPTURE_END;
  data.ddrval = 0;
  if (This->d3dp.cbs1->SceneCapture)
    This->d3dp.cbs1->SceneCapture(&data);
#if 0
  if (data.ddrval == D3D_OK)
    This->surface->unlock_update(This->surface, NULL);
#endif
  hr = data.ddrval;

  This->d3dp.in_scene = FALSE;

  FPS_END_SCENE();
  RESTORE_FPU_STATE();

  return hr;
}

HRESULT WINAPI Direct3DDevice9_Clear(LPDIRECT3DDEVICE9 iface, DWORD dwCount,
				     CONST D3DRECT* lpRects, DWORD dwFlags,
				     D3DCOLOR dwColor, D3DVALUE dvZ,
				     DWORD dwStencil)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CLEAR, 0 };
  D3DHAL_DP2CLEAR clr;
  HRESULT hr;
  BOOL was_in_scene = This->d3dp.in_scene;

  TRACE("(%p)->(%ld,%p,%08lx,%08lx,%f,%ld)\n", This, dwCount, lpRects,
	dwFlags, dwColor, dvZ, dwStencil);
  if (!was_in_scene) IDirect3DDevice9_BeginScene(iface);
  cmd.u.wStateCount = dwCount;
  clr.dwFlags       = dwFlags;
  clr.dwFillColor   = dwColor;
  clr.dvFillDepth   = dvZ;
  clr.dwFillStencil = dwStencil;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &clr, sizeof(clr)-sizeof(RECT),
		      (LPVOID)lpRects, dwCount*sizeof(RECT));
  if (!was_in_scene) IDirect3DDevice9_EndScene(iface);
  return hr;
}

/******************** DRAWPRIMITIVE ********************/

static inline BOOL DP2_Prim(D3DPRIMITIVETYPE d3dptType, DWORD dwCount,
                            LPD3DHAL_DP2COMMAND cmd, UINT* NumVertices)
{
  switch (d3dptType) {
  case D3DPT_POINTLIST:
    FIXME("implement this\n");
    return 0;

  case D3DPT_LINELIST:
    cmd->bCommand = D3DDP2OP_LINELIST;
    cmd->u.wPrimitiveCount = dwCount;
    *NumVertices = dwCount*2;
    break; /* DP2LINELIST == DP2STARTVERTEX */

  case D3DPT_LINESTRIP:
    cmd->bCommand = D3DDP2OP_LINESTRIP;
    cmd->u.wPrimitiveCount = dwCount;
    *NumVertices = dwCount+1;
    break; /* DP2LINESTRIP == DP2STARTVERTEX */

  case D3DPT_TRIANGLELIST:
    cmd->bCommand = D3DDP2OP_TRIANGLELIST;
    cmd->u.wPrimitiveCount = dwCount;
    *NumVertices = dwCount*3;
    break; /* DP2TRIANGLELIST == DP2STARTVERTEX */

  case D3DPT_TRIANGLESTRIP:
    cmd->bCommand = D3DDP2OP_TRIANGLESTRIP;
    cmd->u.wPrimitiveCount = dwCount;
    *NumVertices = dwCount+2;
    break; /* DP2TRIANGLESTRIP == DP2STARTVERTEX */

  case D3DPT_TRIANGLEFAN:
    cmd->bCommand = D3DDP2OP_TRIANGLEFAN;
    cmd->u.wPrimitiveCount = dwCount;
    *NumVertices = dwCount+2;
    break; /* DP2TRIANGLEFAN == DP2STARTVERTEX */

  default:
    return 0;
  }
  return TRUE;
}

static inline BOOL DP2_IndexPrim(D3DPRIMITIVETYPE d3dptType, DWORD dwCount,
                                 LPD3DHAL_DP2COMMAND cmd)
{
  switch (d3dptType) {
  case D3DPT_POINTLIST:
    FIXME("implement this\n");
    return 0;

  case D3DPT_LINELIST:
    cmd->bCommand = D3DDP2OP_INDEXEDLINELIST2;
    cmd->u.wPrimitiveCount = dwCount;
    break; /* emit STARTVERTEX and indices */

  case D3DPT_LINESTRIP:
    cmd->bCommand = D3DDP2OP_INDEXEDLINESTRIP;
    cmd->u.wPrimitiveCount = dwCount;
    break; /* emit STARTVERTEX and indices */

  case D3DPT_TRIANGLELIST:
    cmd->bCommand = D3DDP2OP_INDEXEDTRIANGLELIST2;
    cmd->u.wPrimitiveCount = dwCount;
    break; /* emit STARTVERTEX and indices */

  case D3DPT_TRIANGLESTRIP:
    cmd->bCommand = D3DDP2OP_INDEXEDTRIANGLESTRIP;
    cmd->u.wPrimitiveCount = dwCount;
    break; /* emit STARTVERTEX and indices */

  case D3DPT_TRIANGLEFAN:
    cmd->bCommand = D3DDP2OP_INDEXEDTRIANGLEFAN;
    cmd->u.wPrimitiveCount = dwCount;
    break; /* emit STARTVERTEX and indices */

  default:
    return 0;
  }
  return TRUE;
}

#define DRAW_PRIMITIVE \
  UPDATE_TEXTURES \
  UPDATE_SHADER \
  INIT_STRIDE \
  if (PrimType == D3DPT_POINTLIST) { \
    D3DHAL_DP2POINTS data; \
    cmd.bCommand = D3DDP2OP_POINTS; \
    cmd.u.wPrimitiveCount = 1; \
    NumVertices = PrimitiveCount; \
    data.wCount = PrimitiveCount; \
    data.wVStart = 0; \
    DP2_START \
    return This->emit_dp2(This->ip_ctx, &cmd, NULL, 0, &data, sizeof(data)); \
  } else { \
    D3DHAL_DP2STARTVERTEX data; \
    if (!DP2_Prim(PrimType, PrimitiveCount, &cmd, &NumVertices)) \
      return DDERR_INVALIDPARAMS; \
    data.wVStart = 0; \
    /* if (This->d3dp.renderstate[D3DRENDERSTATE_EXTENTS] */ \
        /* && !(dwFlags & D3DDP_DONOTUPDATEEXTENTS)) */ \
      /* UPDATE_CLIP_STATUS(); */ \
    DP2_START \
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0); \
  }

/* FIXME: this only handles D3DFMT_INDEX16 */

#define DRAW_INDEXED_PRIMITIVE \
  UPDATE_TEXTURES \
  UPDATE_SHADER \
  INIT_STRIDE \
  if (index_format != D3DFMT_INDEX16) { \
    ERR("unsupported index format\n"); \
  } \
  if (PrimType == D3DPT_POINTLIST) { \
    D3DHAL_DP2POINTS *data; \
    unsigned i, dsiz = PrimitiveCount * sizeof(D3DHAL_DP2POINTS); \
    HRESULT hr; \
    cmd.bCommand = D3DDP2OP_POINTS; \
    cmd.u.wPrimitiveCount = PrimitiveCount; \
    data = HeapAlloc(GetProcessHeap(), 0, dsiz); \
    for (i=0; i<PrimitiveCount; i++) { \
      data[i].wCount = 1; \
      data[i].wVStart = ((WORD*)index_data)[i] + BaseIndex; \
    } \
    DP2_START \
    hr = This->emit_dp2(This->ip_ctx, &cmd, NULL, 0, data, dsiz); \
    HeapFree(GetProcessHeap(), 0, data); \
    return hr; \
  } else { \
    D3DHAL_DP2STARTVERTEX data; \
    if (!DP2_IndexPrim(PrimType, PrimitiveCount, &cmd)) \
      return DDERR_INVALIDPARAMS; \
    data.wVStart = BaseIndex; \
    /* if (This->d3dp.renderstate[D3DRENDERSTATE_EXTENTS] */ \
        /* && !(dwFlags & D3DDP_DONOTUPDATEEXTENTS)) */ \
      /* UPDATE_CLIP_STATUS_INDEXED(); */ \
    DP2_START \
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), \
                          (LPVOID)index_data, NumVertices*sizeof(WORD)); \
  }

#if defined(WINE_D3D9_ENABLE_HAL_ALLOCATE_AGP_MEM_FULL)
#define INIT_STRIDE \
  if (!(dx8_hal || ((IDirect3DVertexBuffer9Impl*)(This->state.streamsource[0]))->t.more.dwFVF)) \
    Direct3DDevice9_GetStridedData(iface, &sdata);
#elif defined(WINE_D3D9_ENABLE_HAL_ALLOCATE_AGP_MEM)
#define INIT_STRIDE \
  if (This->d3dp.vertex_shader || !((IDirect3DVertexBuffer9Impl*)(This->state.streamsource[0]))->t.more.dwFVF) \
    Direct3DDevice9_GetStridedData(iface, &sdata);
#else
#define INIT_STRIDE \
    Direct3DDevice9_GetStridedData(iface, &sdata);
#endif

#if defined(WINE_D3D9_ENABLE_HAL_ALLOCATE_AGP_MEM_FULL)
#define DP2_START \
  if (dx8_hal || ((IDirect3DVertexBuffer9Impl*)(This->state.streamsource[0]))->t.more.dwFVF) \
    This->start_dp2(This->ip_ctx, 0, \
                    This->d3dp.vertex_fvf, \
                    &((IDirect3DVertexBuffer9Impl*)(This->state.streamsource[0]))->t.lcl, \
                    StartVertex, NumVertices, 0, 0);  \
  else \
    This->start_dp2_sd(This->ip_ctx, 0, \
                       This->d3dp.vertex_fvf, \
                       NULL, \
                       StartVertex, NumVertices, 0, 0, \
                       &sdata);
#elif defined(WINE_D3D9_ENABLE_HAL_ALLOCATE_AGP_MEM)
#define DP2_START \
  if (This->d3dp.vertex_shader) \
    This->start_dp2_sd(This->ip_ctx, 0, \
                       This->d3dp.vertex_fvf, \
                       NULL, \
                       StartVertex, NumVertices, 0, 0, \
                       &sdata); \
  else if (((IDirect3DVertexBuffer9Impl*)(This->state.streamsource[0]))->t.more.dwFVF) \
    This->start_dp2(This->ip_ctx, 0, \
                    This->d3dp.vertex_fvf, \
                    &((IDirect3DVertexBuffer9Impl*)(This->state.streamsource[0]))->t.lcl, \
                    StartVertex, NumVertices, 0, 0);  \
  else \
    This->start_dp2_sd(This->ip_ctx, 0, \
                       This->d3dp.vertex_fvf, \
                       NULL, \
                       StartVertex, NumVertices, 0, 0, \
                       &sdata);
#else
#define DP2_START \
  This->start_dp2_sd(This->ip_ctx, 0, \
                     This->d3dp.vertex_fvf, \
                     NULL, \
                     StartVertex, NumVertices, 0, 0, \
                     &sdata);
#endif

HRESULT WINAPI Direct3DDevice9_DrawPrimitive(LPDIRECT3DDEVICE9 iface,
					     D3DPRIMITIVETYPE PrimType,
					     UINT StartVertex,
					     UINT PrimitiveCount)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd;
  D3DDRAWPRIMITIVESTRIDEDDATA sdata;
  UINT NumVertices = 0;

  TRACE("(%p)->(%d,%d,%d)\n", This, PrimType,
	StartVertex, PrimitiveCount);
  DRAW_PRIMITIVE
}

HRESULT WINAPI Direct3DDevice9_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 iface,
						    D3DPRIMITIVETYPE PrimType,
						    INT BaseVertexIndex,
						    UINT MinIndex,
						    UINT NumVertices,
						    UINT StartIndex,
						    UINT PrimitiveCount)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd;
  D3DDRAWPRIMITIVESTRIDEDDATA sdata;
  IDirect3DIndexBuffer9Impl* index_buffer = (IDirect3DIndexBuffer9Impl*)(This->state.indices);
  void *index_data = index_buffer->ptr;
  D3DFORMAT index_format = index_buffer->Format;
  UINT BaseIndex = BaseVertexIndex;
  UINT StartVertex = BaseIndex + MinIndex;

  TRACE("(%p)->(%d,%d,%d,%d,%d)\n", This, PrimType,
	MinIndex, NumVertices, StartIndex, PrimitiveCount);
  index_data = ((LPWORD)index_data) + StartIndex;
  DRAW_INDEXED_PRIMITIVE
}

#undef INIT_STRIDE
#define INIT_STRIDE \
  Direct3DDevice9_GetStridedDataUP(iface, &sdata, vertex_data, vertex_stride, FALSE);

#undef DP2_START
#define DP2_START \
  This->start_dp2_sd(This->ip_ctx, D3DHALDP2_USERMEMVERTICES, \
                     This->d3dp.vertex_fvf, \
                     NULL, \
                     0, NumVertices, 0, 0, \
                     &sdata);

HRESULT WINAPI Direct3DDevice9_DrawPrimitiveUP(LPDIRECT3DDEVICE9 iface,
					       D3DPRIMITIVETYPE PrimType,
					       UINT PrimitiveCount,
					       CONST void* vertex_data,
					       UINT vertex_stride)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd;
  D3DDRAWPRIMITIVESTRIDEDDATA sdata;
  UINT NumVertices = 0;

  TRACE("(%p)->(%d,%d,%p,%d)\n", This, PrimType,
	PrimitiveCount, vertex_data, vertex_stride);
  DRAW_PRIMITIVE
}

HRESULT WINAPI Direct3DDevice9_DrawIndexedPrimitiveUP(LPDIRECT3DDEVICE9 iface,
						      D3DPRIMITIVETYPE PrimType,
						      UINT MinIndex,
						      UINT NumVertices,
						      UINT PrimitiveCount,
						      CONST void* index_data,
						      D3DFORMAT index_format,
						      CONST void* vertex_data,
						      UINT vertex_stride)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd;
  D3DDRAWPRIMITIVESTRIDEDDATA sdata;
  UINT BaseIndex = 0;

  TRACE("(%p)->(%d,%d,%d,%d,%p,%d,%p,%d)\n", This, PrimType,
	MinIndex, NumVertices, PrimitiveCount,
	index_data, index_format, vertex_data, vertex_stride);
  DRAW_INDEXED_PRIMITIVE
}

#undef DP2_START
#undef DRAW_INDEXED_PRIMITIVE
#undef DRAW_PRIMITIVE
