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

#include "config.h"

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

#include "d3d9_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

static D3D_Light default_light = {
  {D3DLIGHT_DIRECTIONAL,
   {1,1,1,0},
   {0,0,0,0},
   {0,0,0,0},
   {0,0,0},
   {0,0,1},
   0,0,0,0,0,0,0},
  0, 0, {0,0,0,0}
};

/* table to convert from D3D9 world transform to D3D7 world transform */
static UINT wxfrm[4] = {1,4,5,6};

HRESULT WINAPI Direct3DDevice9_SetTransform(LPDIRECT3DDEVICE9 iface,
					    D3DTRANSFORMSTATETYPE dtstType,
					    CONST D3DMATRIX* lpD3DMatrix)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETTRANSFORM, 0, {1} };
  D3DHAL_DP2SETTRANSFORM xfrm;

  TRACE("(%p)->(%d,%p)\n", This, dtstType, lpD3DMatrix);
  if (!dtstType) return D3DERR_INVALIDCALL;

  if (dtstType >= 256) {
    unsigned i = dtstType - 256;
    if (i >= 4) return D3DERR_INVALIDCALL;
    dtstType = wxfrm[dtstType-256];
  }

  if (dtstType >= D3DTS_TEXTURE0) {
    unsigned i = dtstType - D3DTS_TEXTURE0;
    if (i >= 8) return D3DERR_INVALIDCALL;
    This->d3dp.txfrm[i] = *lpD3DMatrix;
  }
  else if (dtstType < 8) {
    This->d3dp.xfrm[dtstType] = *lpD3DMatrix;
  }
  xfrm.xfrmType = dtstType;
  xfrm.matrix = *lpD3DMatrix;
  return This->emit_dp2(This->ip_ctx, &cmd, &xfrm, sizeof(xfrm), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_GetTransform(LPDIRECT3DDEVICE9 iface,
					    D3DTRANSFORMSTATETYPE dtstType,
					    D3DMATRIX* lpD3DMatrix)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%d,%p)\n", This, dtstType, lpD3DMatrix);
  if (!dtstType) return D3DERR_INVALIDCALL;

  if (dtstType >= 256) {
    unsigned i = dtstType - 256;
    if (i >= 4) return D3DERR_INVALIDCALL;
    dtstType = wxfrm[dtstType-256];
  }

  if (dtstType >= D3DTS_TEXTURE0) {
    unsigned i = dtstType - D3DTS_TEXTURE0;
    if (i >= 8) return D3DERR_INVALIDCALL;
    *lpD3DMatrix = This->d3dp.txfrm[i];
  }
  else if (dtstType < 8) {
    *lpD3DMatrix = This->d3dp.xfrm[dtstType];
  }
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetViewport(LPDIRECT3DDEVICE9 iface,
					   CONST D3DVIEWPORT9* lpData)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { 0, 0, {1} };
  D3DHAL_DP2VIEWPORTINFO view;
  D3DHAL_DP2ZRANGE zrange;
  HRESULT hr;

  TRACE("(%p)->(%p)\n", This, lpData);
  memcpy(&This->d3dp.viewport, lpData, sizeof(D3DVIEWPORT7));
  view.dwX = lpData->X;
  view.dwY = lpData->Y;
  view.dwWidth  = lpData->Width;
  view.dwHeight = lpData->Height;
  zrange.dvMinZ = lpData->MinZ;
  zrange.dvMaxZ = lpData->MaxZ;
  cmd.bCommand = D3DDP2OP_VIEWPORTINFO;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &view, sizeof(view), NULL, 0);
  /* FIXME: do we also need to do anything about WINFO? */
  if (SUCCEEDED(hr)) {
      cmd.bCommand = D3DDP2OP_ZRANGE;
      hr = This->emit_dp2(This->ip_ctx, &cmd, &zrange, sizeof(zrange), NULL, 0);
  }
  return hr;
}

HRESULT WINAPI Direct3DDevice9_GetViewport(LPDIRECT3DDEVICE9 iface,
					   D3DVIEWPORT9* lpData)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%p)\n", This, lpData);
  memcpy(lpData, &This->d3dp.viewport, sizeof(D3DVIEWPORT7));
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetMaterial(LPDIRECT3DDEVICE9 iface,
					   CONST D3DMATERIAL9* lpMat)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETMATERIAL, 0, {1} };

  TRACE("(%p)->(%p)\n", This, lpMat);
#if 0
  _dump_colorvalue("Diffuse", lpMat->u.diffuse);
  _dump_colorvalue("Ambient", lpMat->u1.ambient);
  _dump_colorvalue("Specular", lpMat->u2.specular);
  _dump_colorvalue("Emissive", lpMat->u3.emissive);
  TRACE(" Power : %f\n", lpMat->u4.power);
#endif
  return This->emit_dp2(This->ip_ctx, &cmd,
			(D3DMATERIAL7*)lpMat, sizeof(This->d3dp.material), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_GetMaterial(LPDIRECT3DDEVICE9 iface,
					   D3DMATERIAL9* lpMat)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%p)\n", This, lpMat);
  *(D3DMATERIAL7*)lpMat = This->d3dp.material;
  return D3D_OK;
}

static HRESULT CheckExpandLights(IDirect3DDevice9Impl *This, DWORD dwIndex)
{
  if (dwIndex >= This->d3dp.numlights) {
    DWORD newlights = dwIndex+1, cnt;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CREATELIGHT, 0, {1} };
    D3DHAL_DP2CREATELIGHT data;
    D3D_Light *newlight;
    HRESULT hr;

    newlight = HeapReAlloc(GetProcessHeap(),0,This->d3dp.light,sizeof(D3D_Light)*newlights);
    if (!newlight) {
      ERR("out of memory\n");
      return DDERR_OUTOFMEMORY;
    }
    for (cnt=This->d3dp.numlights; cnt<newlights; cnt++)
      memcpy(&newlight[cnt], &default_light, sizeof(D3D_Light));
    data.dwIndex = dwIndex;
    hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
    if (FAILED(hr)) {
      ERR("CREATELIGHT failed (%ld)\n", hr);
      return hr;
    }
    This->d3dp.light = newlight;
    This->d3dp.numlights = newlights;
  }
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetLight(LPDIRECT3DDEVICE9 iface,
					DWORD dwIndex,
					CONST D3DLIGHT9* lpLight)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETLIGHT, 0, {1} };
  D3DHAL_DP2SETLIGHT data;
  HRESULT hr;

  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpLight);
  hr = CheckExpandLights(This, dwIndex);
  if (FAILED(hr)) return hr;
  This->d3dp.light[dwIndex].l = *(D3DLIGHT7*)lpLight;
  data.dwIndex = dwIndex;
  data.dwDataType = D3DHAL_SETLIGHT_DATA;
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data),
			&This->d3dp.light[dwIndex].l, sizeof(D3DLIGHT7));
}

HRESULT WINAPI Direct3DDevice9_GetLight(LPDIRECT3DDEVICE9 iface,
					DWORD dwIndex,
					D3DLIGHT9* lpLight)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpLight);
  if (dwIndex < This->d3dp.numlights)
    *(D3DLIGHT7*)lpLight = This->d3dp.light[dwIndex].l;
  else
    *(D3DLIGHT7*)lpLight = default_light.l;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_LightEnable(LPDIRECT3DDEVICE9 iface,
					   DWORD dwIndex,
					   BOOL bEnable)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETLIGHT, 0, {1} };
  D3DHAL_DP2SETLIGHT data;
  DWORD oldflags;
  HRESULT hr;

  TRACE("(%p)->(%ld,%d)\n", This, dwIndex, bEnable);
  hr = CheckExpandLights(This, dwIndex);
  if (FAILED(hr)) return hr;
  oldflags = This->d3dp.light[dwIndex].flags;
  if (bEnable) {
    This->d3dp.light[dwIndex].flags |= D3DLIGHT_ACTIVE;
    data.dwDataType = D3DHAL_SETLIGHT_ENABLE;
  } else {
    This->d3dp.light[dwIndex].flags &= ~D3DLIGHT_ACTIVE;
    data.dwDataType = D3DHAL_SETLIGHT_DISABLE;
  }
  data.dwIndex = dwIndex;
  if (This->d3dp.light[dwIndex].flags == oldflags) return D3D_OK;
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_GetLightEnable(LPDIRECT3DDEVICE9 iface,
					      DWORD dwIndex,
					      LPBOOL lpbEnable)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpbEnable);
  if (dwIndex < This->d3dp.numlights)
    *lpbEnable = This->d3dp.light[dwIndex].flags & D3DLIGHT_ACTIVE;
  else
    *lpbEnable = FALSE;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetClipPlane(LPDIRECT3DDEVICE9 iface,
					    DWORD dwIndex,
					    CONST float* pPlane)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETCLIPPLANE, 0, {1} };
  D3DHAL_DP2SETCLIPPLANE data;

  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, pPlane);
  memcpy(&This->d3dp.clipplane[dwIndex], pPlane, sizeof(float)*4);
  data.dwIndex = dwIndex;
  memcpy(&data.plane, pPlane, sizeof(float)*4);
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_GetClipPlane(LPDIRECT3DDEVICE9 iface,
					    DWORD dwIndex,
					    float* pPlane)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, pPlane);
  memcpy(pPlane, &This->d3dp.clipplane[dwIndex], sizeof(float)*4);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetRenderState(LPDIRECT3DDEVICE9 iface,
					      D3DRENDERSTATETYPE d3dType,
					      DWORD dwState)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_RENDERSTATE, 0, {1} };
  D3DHAL_DP2RENDERSTATE stat;

  TRACE("(%p)->(%d,%08lx)\n", This, d3dType, dwState);
  if (d3dType >= 256) {
    WARN("out-of-range render state (%d)\n", d3dType);
    return D3DERR_INVALIDCALL;
  }
  /* according to the HAL docs, it seems the driver is
   * responsible for updating the render state array
   * to what it's capable of handling */
  stat.RenderState = d3dType;
  stat.u.dwState = dwState;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_GetRenderState(LPDIRECT3DDEVICE9 iface,
					      D3DRENDERSTATETYPE d3dType,
					      LPDWORD lpdwState)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  if (d3dType >= 256) {
    WARN("out-of-range render state (%d)\n", d3dType);
    return D3DERR_INVALIDCALL;
  }
  *lpdwState = This->d3dp.renderstate[d3dType];
  TRACE("(%p)->(%d,%p) => %08lx\n", This, d3dType, lpdwState, *lpdwState);
  return D3D_OK;
}

static ICOM_VTABLE(IDirect3DStateBlock9) D3DSB9_Vtbl;

HRESULT WINAPI Direct3DDevice9_BeginStateBlock(LPDIRECT3DDEVICE9 iface)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3D9StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)\n", This);
  if (This->current->state_block) return D3DERR_INVALIDCALL;

  block = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(D3D9StateBlock));
  block->lpVtbl = &D3DSB9_Vtbl;
  block->ref = 1;
  block->parent = This;

  hr = This->alloc_state_block(This->ip_ctx, &block->state_block);
  if (FAILED(hr)) {
    HeapFree(GetProcessHeap(), 0, block);
    return hr;
  }

  data.dwOperation = D3DHAL_STATESETBEGIN;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (SUCCEEDED(hr)) This->current = block;
  else HeapFree(GetProcessHeap(), 0, block);
  return hr;
}

HRESULT WINAPI Direct3DDevice9_EndStateBlock(LPDIRECT3DDEVICE9 iface,
					     IDirect3DStateBlock9** ppSB)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3D9StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;

  TRACE("(%p)->(%p)\n", This, ppSB);
  if (!This->current->state_block) return D3DERR_INVALIDCALL;

  block = This->current;
  *ppSB = (LPDIRECT3DSTATEBLOCK9)block;
  This->current = &This->state;
  TRACE(" => %p\n", *ppSB);

  data.dwOperation = D3DHAL_STATESETEND;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

HRESULT WINAPI Direct3DStateBlock9_Apply(LPDIRECT3DSTATEBLOCK9 iface)
{
  ICOM_THIS(D3D9StateBlock, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)->()\n", This);
  if (This->parent->current->state_block) return D3DERR_INVALIDCALL;

  data.dwOperation = D3DHAL_STATESETEXECUTE;
  data.dwParam = This->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (SUCCEEDED(hr)) {
    /* update current state to match new device state */
    unsigned u;
    for (u=0; u<8; u++) if (This->dirty_tex & (1<<u)) {
      if (This->parent->state.tex[u] != This->tex[u]) {
        if (This->tex[u]) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)This->tex[u]);
        if (This->parent->state.tex[u]) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->parent->state.tex[u]);
        This->parent->state.tex[u] = This->tex[u];
      }
    }
    This->parent->dirty_textures = TRUE; /* just in case */
    for (u=0; u<16; u++) if (This->dirty_stream & (1<<u)) {
      if (This->parent->state.streamsource[u] != This->streamsource[u]) {
        if (This->streamsource[u]) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)This->streamsource[u]);
        if (This->parent->state.streamsource[u]) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->parent->state.streamsource[u]);
        This->parent->state.streamsource[u] = This->streamsource[u];
      }
      This->parent->state.streamstride[u] = This->streamstride[u];
      This->parent->d3dp.stream[u].data_t = &((IDirect3DVertexBuffer9Impl*)(This->streamsource[u]))->t;
      This->parent->d3dp.stream[u].stride = This->streamstride[u];
    }
    if (This->dirty_indices) {
      if (This->parent->state.indices != This->indices) {
        if (This->indices) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)This->indices);
        if (This->parent->state.indices) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->parent->state.indices);
        This->parent->state.indices = This->indices;
      }
    }
    if (This->dirty_fvf) {
      This->parent->state.vertex_fvf = This->vertex_fvf;
      This->parent->d3dp.vertex_fvf = This->vertex_fvf;
    }
    if (This->dirty_vdecl) {
      if (This->parent->state.vertex_decl != This->vertex_decl) {
        if (This->vertex_decl) IDirect3DVertexDeclaration9_AddRef(This->vertex_decl);
        if (This->parent->state.vertex_decl) IDirect3DVertexDeclaration9_Release(This->parent->state.vertex_decl);
        This->parent->state.vertex_decl = This->vertex_decl;
        if (This->vertex_decl)
          This->parent->d3dp.vertex_fvf = ((IDirect3DVertexDeclaration9Impl*)This->vertex_decl)->FVF;
      }
    }
    if (This->dirty_vshader) {
      if (This->parent->state.vertex_shader != This->vertex_shader) {
        if (This->vertex_shader) IDirect3DVertexShader9_AddRef(This->vertex_shader);
        if (This->parent->state.vertex_shader) IDirect3DVertexShader9_Release(This->parent->state.vertex_shader);
        This->parent->state.vertex_shader = This->vertex_shader;
      }
    }
    if (This->dirty_vdecl && This->dirty_vshader &&
        (This->parent->state.vertex_decl || This->parent->state.vertex_shader)) {
      This->parent->d3dp.vertex_shader = NULL; /* force reload */
    }
    /* we could also do states that the driver doesn't know about here */
  }
  return hr;
}

HRESULT WINAPI Direct3DStateBlock9_Capture(LPDIRECT3DSTATEBLOCK9 iface)
{
  ICOM_THIS(D3D9StateBlock, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)->()\n", This);
  if (This->parent->current->state_block) return D3DERR_INVALIDCALL;

  data.dwOperation = D3DHAL_STATESETCAPTURE;
  data.dwParam = This->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (SUCCEEDED(hr)) {
    /* update state block to match current state */
    unsigned u;
    for (u=0; u<8; u++) if (This->dirty_tex & (1<<u)) {
      if (This->parent->state.tex[u] != This->tex[u]) {
        if (This->parent->state.tex[u]) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)This->parent->state.tex[u]);
        if (This->tex[u]) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->tex[u]);
        This->tex[u] = This->parent->state.tex[u];
      }
    }
    if (This->dirty_fvf) {
      This->vertex_fvf = This->parent->state.vertex_fvf;
    }
    if (This->dirty_vdecl) {
      if (This->parent->state.vertex_decl != This->vertex_decl) {
        if (This->parent->state.vertex_decl) IDirect3DVertexDeclaration9_AddRef(This->parent->state.vertex_decl);
        if (This->vertex_decl) IDirect3DVertexDeclaration9_Release(This->vertex_decl);
        This->vertex_decl = This->parent->state.vertex_decl;
      }
    }
    if (This->dirty_vshader) {
      if (This->parent->state.vertex_shader != This->vertex_shader) {
        if (This->parent->state.vertex_shader) IDirect3DVertexShader9_AddRef(This->parent->state.vertex_shader);
        if (This->vertex_shader) IDirect3DVertexShader9_Release(This->vertex_shader);
        This->vertex_shader = This->parent->state.vertex_shader;
      }
    }
    for (u=0; u<16; u++) if (This->dirty_stream & (1<<u)) {
      if (This->parent->state.streamsource[u] != This->streamsource[u]) {
        if (This->parent->state.streamsource[u]) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)This->parent->state.streamsource[u]);
        if (This->streamsource[u]) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->streamsource[u]);
        This->streamsource[u] = This->parent->state.streamsource[u];
      }
      This->streamstride[u] = This->parent->state.streamstride[u];
    }
    if (This->dirty_indices) {
      if (This->parent->state.indices != This->indices) {
        if (This->parent->state.indices) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)This->parent->state.indices);
        if (This->indices) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->indices);
        This->indices = This->parent->state.indices;
      }
    }
    /* we could also do states that the driver doesn't know about here */
  }
  return hr;
}

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

ULONG WINAPI Direct3DStateBlock9_AddRef(LPDIRECT3DSTATEBLOCK9 iface)
{
  ICOM_THIS(D3D9StateBlock, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref;
}

ULONG WINAPI Direct3DStateBlock9_Release(LPDIRECT3DSTATEBLOCK9 iface)
{
  ICOM_THIS(D3D9StateBlock, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)->()\n", iface);
  if (--This->ref) return This->ref;

  data.dwOperation = D3DHAL_STATESETDELETE;
  data.dwParam = This->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (FAILED(hr)) return hr;
  hr = This->parent->free_state_block(This->parent->ip_ctx, This->state_block);
  if (SUCCEEDED(hr)) {
    /* release referenced objects */
    unsigned u;
    for (u=0; u<8; u++)
      if (This->tex[u]) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->tex[u]);
    for (u=0; u<16; u++)
      if (This->streamsource[u]) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->streamsource[u]);
    if (This->indices) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)This->indices);
    if (This->vertex_decl) IDirect3DVertexDeclaration9_Release(This->vertex_decl);
    if (This->vertex_shader) IDirect3DVertexShader9_Release(This->vertex_shader);
    HeapFree(GetProcessHeap(), 0, This);
  }
  return 0;
}

HRESULT WINAPI Direct3DStateBlock9_GetDevice(LPDIRECT3DSTATEBLOCK9 iface,
					     IDirect3DDevice9** ppDevice)
{
  ICOM_THIS(D3D9StateBlock, iface);
  TRACE("(%p)->(%p)\n", iface, ppDevice);
  *ppDevice = (IDirect3DDevice9*)This->parent;
  IDirect3DDevice9_AddRef(*ppDevice);
  return D3D_OK;
}

static ICOM_VTABLE(IDirect3DStateBlock9) D3DSB9_Vtbl =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DStateBlock9_QueryInterface,
  Direct3DStateBlock9_AddRef,
  Direct3DStateBlock9_Release,
  Direct3DStateBlock9_GetDevice,
  Direct3DStateBlock9_Capture,
  Direct3DStateBlock9_Apply
};

static const D3DTEXTURESTAGESTATETYPE sampler_type[16];

HRESULT WINAPI Direct3DDevice9_CreateStateBlock(LPDIRECT3DDEVICE9 iface,
						D3DSTATEBLOCKTYPE type,
						IDirect3DStateBlock9** ppSB)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  HRESULT hr;

  FIXME("(%p)->(0x%08x, %p): incomplete\n", iface, type, ppSB);
  hr = IDirect3DDevice9_BeginStateBlock(iface);
  if (FAILED(hr)) return hr;

#define RESET_CURRENT_RENDER_STATE(state) \
    IDirect3DDevice9_SetRenderState(iface, state, This->d3dp.renderstate[state])
#define RESET_CURRENT_TEXTURE_STAGE_STATE(state) \
    IDirect3DDevice9_SetTextureStageState(iface, stage, state, This->d3dp.texstagestate[stage][state])
#define RESET_CURRENT_SAMPLER_STATE(state) \
    IDirect3DDevice9_SetSamplerState(iface, stage, state, This->d3dp.texstagestate[stage][sampler_type[state]])

  if (type == D3DSBT_ALL)
  {
    int stage, stream;
    int clipplane;

    TRACE("doing all\n");

    /* current render states */
    RESET_CURRENT_RENDER_STATE(D3DRS_CULLMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_SPECULARENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGCOLOR);
    RESET_CURRENT_RENDER_STATE(D3DRS_RANGEFOGENABLE);

    RESET_CURRENT_RENDER_STATE(D3DRS_DEBUGMONITORTOKEN);
    RESET_CURRENT_RENDER_STATE(D3DRS_POSITIONDEGREE);
    RESET_CURRENT_RENDER_STATE(D3DRS_NORMALDEGREE);
    /* others in pixel state / vertex state */

    /* current textures */
    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
      IDirect3DDevice9_SetTexture(iface, stage, This->state.tex[stage]);

    /* FIXME: current palette */
    FIXME("reset palette\n");

    /* current streams */
    for (stream = 0; stream < 16; stream++)
    {
      IDirect3DDevice9_SetStreamSource(iface, stream, This->state.streamsource[stream],
                                       0, This->state.streamstride[stream]);
    }

    /* current viewport */
    IDirect3DDevice9_SetViewport(iface, (D3DVIEWPORT9*)&(This->d3dp.viewport));

    /* current transforms */
    IDirect3DDevice9_SetTransform(iface, D3DTS_PROJECTION, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_PROJECTION]));
    IDirect3DDevice9_SetTransform(iface, D3DTS_VIEW, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_VIEW]));
    IDirect3DDevice9_SetTransform(iface, D3DTS_WORLD, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD]));
    IDirect3DDevice9_SetTransform(iface, D3DTS_WORLD1, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD1]));
    IDirect3DDevice9_SetTransform(iface, D3DTS_WORLD2, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD2]));
    IDirect3DDevice9_SetTransform(iface, D3DTS_WORLD3, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD3]));
    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
      IDirect3DDevice9_SetTransform(iface, D3DTS_TEXTURE0+stage, &(This->d3dp.txfrm[stage]));

    /* current clipplanes */
    for (clipplane = 0; clipplane < This->caps->MaxUserClipPlanes; clipplane++)
    {
      IDirect3DDevice9_SetClipPlane(iface, clipplane, (float*)&(This->d3dp.clipplane[clipplane]));
    }

    /* current material */
    IDirect3DDevice9_SetMaterial(iface, (D3DMATERIAL9*)&(This->d3dp.material));

    /* FIXME: current pixel shader */
    /* FIXME: current pixel shader constants */
  }

  if (type == D3DSBT_PIXELSTATE || type == D3DSBT_ALL)
  {
    int stage;

    TRACE("doing pixel\n");

    /* FIXME: pixel shaders! */
    /* FIXME: current pixel shader constants */
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHABLENDENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHAFUNC);
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHAREF);
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHATESTENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_BLENDOP);
    RESET_CURRENT_RENDER_STATE(D3DRS_COLORWRITEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_DESTBLEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_DITHERENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FILLMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGDENSITY);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGSTART);
    RESET_CURRENT_RENDER_STATE(D3DRS_LASTPIXEL);
    RESET_CURRENT_RENDER_STATE(D3DRS_SHADEMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_SRCBLEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILFAIL);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILFUNC);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILMASK);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILPASS);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILREF);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILWRITEMASK);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILZFAIL);
    RESET_CURRENT_RENDER_STATE(D3DRS_TEXTUREFACTOR);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP0);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP1);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP2);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP3);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP4);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP5);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP6);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP7);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZFUNC);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZWRITEENABLE);

    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
    {
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAARG0);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAARG1);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAARG2);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAOP);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVLOFFSET);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVLSCALE);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT00);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT01);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT10);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT11);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLORARG0);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLORARG1);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLORARG2);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLOROP);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_RESULTARG);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXCOORDINDEX);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXTURETRANSFORMFLAGS);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_ADDRESSU);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_ADDRESSV);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_ADDRESSW);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_BORDERCOLOR);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_MAGFILTER);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_MAXANISOTROPY);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_MAXMIPLEVEL);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_MINFILTER);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_MIPFILTER);
      RESET_CURRENT_SAMPLER_STATE(D3DSAMP_MIPMAPLODBIAS);
    }
  }

  if (type == D3DSBT_VERTEXSTATE || type == D3DSBT_ALL)
  {
    int stage, light;
    IDirect3DVertexShader9* vertexshader;

    TRACE("doing vertex\n");

    RESET_CURRENT_RENDER_STATE(D3DRS_AMBIENT);
    RESET_CURRENT_RENDER_STATE(D3DRS_AMBIENTMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_CLIPPING);
    RESET_CURRENT_RENDER_STATE(D3DRS_CLIPPLANEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_COLORVERTEX);
    RESET_CURRENT_RENDER_STATE(D3DRS_DIFFUSEMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_EMISSIVEMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGDENSITY);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGSTART);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGTABLEMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGVERTEXMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_INDEXEDVERTEXBLENDENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_LIGHTING);
    RESET_CURRENT_RENDER_STATE(D3DRS_LOCALVIEWER);
    RESET_CURRENT_RENDER_STATE(D3DRS_MULTISAMPLEANTIALIAS);
    RESET_CURRENT_RENDER_STATE(D3DRS_MULTISAMPLEMASK);
    RESET_CURRENT_RENDER_STATE(D3DRS_NORMALIZENORMALS);
    RESET_CURRENT_RENDER_STATE(D3DRS_PATCHEDGESTYLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALE_A);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALE_B);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALE_C);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSIZE);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSIZE_MAX);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSIZE_MIN);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSPRITEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_SPECULARMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_TWEENFACTOR);
    RESET_CURRENT_RENDER_STATE(D3DRS_VERTEXBLEND);

    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
    {
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXCOORDINDEX);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXTURETRANSFORMFLAGS);
    }

    /* lights: */
    for (light = 0; light < This->d3dp.numlights; light++)
    {
      IDirect3DDevice9_SetLight(iface, light, (D3DLIGHT9*)&(This->d3dp.light[light].l));
      IDirect3DDevice9_LightEnable(iface, light, (This->d3dp.light[light].flags & D3DLIGHT_ACTIVE));
    }

    /* vertex shader */
    IDirect3DDevice9_GetVertexShader(iface, &vertexshader);
    IDirect3DDevice9_SetVertexShader(iface, vertexshader);
    IDirect3DVertexShader9_Release(vertexshader);
    /* current vertex shader constants */
    IDirect3DDevice9_SetVertexShaderConstantF(iface, 0, This->d3dp.vertex_const,
                                              This->caps->MaxVertexShaderConst);
  }
#undef RESET_CURRENT_RENDER_STATE
#undef RESET_CURRENT_TEXTURE_STAGE_STATE

  hr = IDirect3DDevice9_EndStateBlock(iface, ppSB);

  return hr;
}

HRESULT WINAPI Direct3DDevice9_GetTexture(LPDIRECT3DDEVICE9 iface,
					  DWORD dwStage,
					  LPDIRECT3DBASETEXTURE9* lplpTexture)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  *lplpTexture = This->state.tex[dwStage];
  TRACE("(%p)->(%ld,%p) => %p\n", This, dwStage, lplpTexture, *lplpTexture);
  if (*lplpTexture) IDirect3DBaseTexture9_AddRef(*lplpTexture);
  return D3D_OK;
}

ULONG WINAPI IDirect3DResource9_AddRefInternal(LPDIRECT3DRESOURCE9 iface)
{
  ICOM_THIS(IDirect3DResource9Impl, iface);

  TRACE("(%p)->()\n", iface);
  This->intref++;
  return IDirect3DResource9_AddRef(iface);
}

ULONG WINAPI IDirect3DResource9_ReleaseInternal(LPDIRECT3DRESOURCE9 iface)
{ 
  ICOM_THIS(IDirect3DResource9Impl, iface);

  TRACE("(%p)->()\n", iface);
  This->intref--;
  return IDirect3DResource9_Release(iface);
}
  
HRESULT WINAPI Direct3DDevice9_SetTexture(LPDIRECT3DDEVICE9 iface,
					  DWORD dwStage,
					  LPDIRECT3DBASETEXTURE9 lpTexture)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  LPDIRECT3DBASETEXTURE9 oldtex;
  IDirect3DBaseTexture9Impl* tex = (IDirect3DBaseTexture9Impl*)lpTexture;
  /* in reality, Direct3D allocates "surface handles" that it
   * uses for the state here, but since we don't have the memory
   * restrictions that real drivers do (and we're very lazy too),
   * we'll just cast real pointers here, at least for now */
  DWORD dwState = 0;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXTURESTAGESTATE, 0, {1} };
  D3DHAL_DP2TEXTURESTAGESTATE stat;

  TRACE("(%p)->(%ld,%p)\n", This, dwStage, lpTexture);

  oldtex = This->current->tex[dwStage];
  if ((lpTexture == oldtex) && (This->current->dirty_tex & (1<<dwStage))) return D3D_OK;

  /* maintain reference count - but the reference count should be hidden 
   * from the app (as per testing on NT2k DX8.1) so an internal refcount
   * is used */
  if (lpTexture) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)lpTexture);
  This->current->tex[dwStage] = lpTexture;
  This->current->dirty_tex |= 1<<dwStage;
  if (oldtex) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)oldtex);

  /* find texture object's first surface */
  if (lpTexture)
    dwState = TEXTURE_HANDLE(tex);

  if (!This->current->state_block) {
    This->d3dp.texstagestate[dwStage][D3DTSS_TEXTUREMAP] = dwState;
    if (lpTexture && tex->IsDirty) This->dirty_textures = TRUE;
    TRACE("saving\n");
  }
  TRACE("val=%08lx\n", dwState);
  stat.wStage = dwStage;
  stat.TSState = D3DTSS_TEXTUREMAP;
  stat.dwValue = dwState;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_GetTextureStageState(LPDIRECT3DDEVICE9 iface,
						    DWORD dwStage,
						    D3DTEXTURESTAGESTATETYPE d3dType,
						    LPDWORD lpdwState)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  DWORD dwState;

  if (d3dType >= 64) {
    WARN("out-of-range texture stage state %d\n", d3dType);
    return D3DERR_INVALIDCALL;
  }
  dwState = This->d3dp.texstagestate[dwStage][d3dType];

  TRACE("(%p)->(%ld,%d,%p) => %ld\n", This, dwStage, d3dType, lpdwState, dwState);
  *lpdwState = dwState;

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetTextureStageState(LPDIRECT3DDEVICE9 iface,
						    DWORD dwStage,
						    D3DTEXTURESTAGESTATETYPE d3dType,
						    DWORD dwState)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXTURESTAGESTATE, 0, {1} };
  D3DHAL_DP2TEXTURESTAGESTATE stat;

  TRACE("(%p)->(%ld,%d,%ld)\n", This, dwStage, d3dType, dwState);

  if (d3dType >= 64) {
    WARN("out-of-range texture stage state %d\n", d3dType);
    return D3DERR_INVALIDCALL;
  }

  /* the doc implies that the app is allowed to set any state it wants,
   * _then_ check (with ValidateDevice) whether the hardware groks the
   * resulting state vector, so I guess no checks should be performed here
   */
  if (!This->current->state_block)
    This->d3dp.texstagestate[dwStage][d3dType] = dwState;
  stat.wStage = dwStage;
  stat.TSState = d3dType;
  stat.dwValue = dwState;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

static const D3DTEXTURESTAGESTATETYPE sampler_type[16] = {
  0,
  13, /* ADDRESSU */
  14, /* ADDRESSV */
  25, /* ADDRESSW */
  15, /* BORDERCOLOR */
  16, /* MAGFILTER */
  17, /* MINFILTER */
  18, /* MIPFILTER */
  19, /* MIPMAPLODBIAS */
  20, /* MAXMIPLEVEL */
  21  /* MAXANISOTROPY */
  /* FIXME: SRGBTEXTURE, ELEMENTINDEX, DMAPOFFSET */
};

HRESULT WINAPI Direct3DDevice9_GetSamplerState(LPDIRECT3DDEVICE9 iface,
					       DWORD Sampler,
					       D3DSAMPLERSTATETYPE Type,
					       DWORD* pValue)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DTEXTURESTAGESTATETYPE d3dType = sampler_type[Type];
  DWORD Value;

  /* tables to convert from D3D7 texture filters to D3D9 texture filters */
  static const DWORD magfilter[6] = {0, 1, 2, 6, 7, 3};
  static const DWORD minfilter[4] = {0, 1, 2, 3};
  static const DWORD mipfilter[4] = {0, 0, 1, 2};

  if (Type >= 16) {
    WARN("out-of-range sampler type %d\n", Type);
    return D3DERR_INVALIDCALL;
  }

  if (!d3dType && Type) {
    WARN("unsupported sampler type %d\n", Type);
    /* return D3DERR_INVALIDCALL; */
    *pValue = 0;
    return D3D_OK;
  }

  Value = This->d3dp.texstagestate[Sampler][d3dType];

  /* convert from D3D7 (HAL) texture filters to D3D9 texture filters */
  switch (Type) {
  case D3DSAMP_MAGFILTER:
    Value = magfilter[Value];
    break;
  case D3DSAMP_MINFILTER:
    Value = minfilter[Value];
    break;
  case D3DSAMP_MIPFILTER:
    Value = mipfilter[Value];
    break;
  default:
    break;
  }

  TRACE("(%p)->(%ld,%d,%p) => %ld\n", This, Sampler, Type, pValue, Value);
  *pValue = Value;

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetSamplerState(LPDIRECT3DDEVICE9 iface,
					       DWORD Sampler,
					       D3DSAMPLERSTATETYPE Type,
					       DWORD Value)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXTURESTAGESTATE, 0, {1} };
  D3DHAL_DP2TEXTURESTAGESTATE stat;
  D3DTEXTURESTAGESTATETYPE d3dType = sampler_type[Type];

  /* tables to convert from D3D9 texture filters to D3D7 texture filters */
  static const DWORD magfilter[8] = {0, 1, 2, 5, 3, 4, 3, 4};
  static const DWORD minfilter[8] = {0, 1, 2, 3, 3, 3, 3, 3};
  static const DWORD mipfilter[8] = {1, 2, 3, 3, 3, 3, 3, 3};

  TRACE("(%p)->(%ld,%d,%ld)\n", This, Sampler, Type, Value);

  if (Type >= 16) {
    WARN("out-of-range sampler type %d\n", Type);
    return D3DERR_INVALIDCALL;
  }

  if (!d3dType && Type) {
    WARN("unsupported sampler type %d\n", Type);
    /* return D3DERR_INVALIDCALL; */
    return D3D_OK;
  }

  /* convert from D3D9 texture filters to D3D7 (HAL) texture filters */
  switch (Type) {
  case D3DSAMP_MAGFILTER:
    Value = magfilter[Value];
    break;
  case D3DSAMP_MINFILTER:
    Value = minfilter[Value];
    break;
  case D3DSAMP_MIPFILTER:
    Value = mipfilter[Value];
    break;
  default:
    break;
  }

  if (!This->current->state_block)
    This->d3dp.texstagestate[Sampler][d3dType] = Value;
  stat.wStage = Sampler;
  stat.TSState = d3dType;
  stat.dwValue = Value;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI Direct3DDevice9_ValidateDevice(LPDIRECT3DDEVICE9 iface,
					      DWORD* pNumPasses)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%p)\n", This, pNumPasses);
  return This->validate_device(This->ip_ctx, pNumPasses);
}

HRESULT WINAPI Direct3DDevice9_SetStreamSource(LPDIRECT3DDEVICE9 iface,
					       UINT StreamNumber,
					       LPDIRECT3DVERTEXBUFFER9 pStreamData,
					       UINT OffsetInBytes,
					       UINT Stride)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  LPDIRECT3DVERTEXBUFFER9 olddata;
  IDirect3DVertexBuffer9Impl* buffer = (IDirect3DVertexBuffer9Impl *)pStreamData;

  TRACE("(%p)->(%d,%p,%d)\n", This, StreamNumber, pStreamData, Stride);

  if (OffsetInBytes) {
    FIXME("offset=%d\n", OffsetInBytes);
    return D3DERR_INVALIDCALL;
  }

  olddata = This->current->streamsource[StreamNumber];

  if (pStreamData != olddata) {
    if (pStreamData) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)pStreamData);
    This->current->streamsource[StreamNumber] = pStreamData;
    if (olddata) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)olddata);
  }
  This->current->streamstride[StreamNumber] = Stride;
  This->current->dirty_stream |= 1<<StreamNumber;

  This->d3dp.stream[StreamNumber].data_t = &(buffer->t);
  This->d3dp.stream[StreamNumber].stride = Stride;

  if (dx8_hal) {
    /* FIXME: should emit D3DHAL_DP2SETSTREAMSOURCE */
  }

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_GetStreamSource(LPDIRECT3DDEVICE9 iface,
					       UINT StreamNumber,
					       LPDIRECT3DVERTEXBUFFER9* ppStreamData,
					       UINT* pOffsetInBytes,
					       UINT* pStride)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  *ppStreamData = This->state.streamsource[StreamNumber];
  *pOffsetInBytes = 0;
  *pStride = This->state.streamstride[StreamNumber];
  TRACE("(%p)->(%d,%p,%p)\n", This, StreamNumber, ppStreamData, pStride);
  if (*ppStreamData) IDirect3DVertexBuffer9_AddRef(*ppStreamData);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetIndices(LPDIRECT3DDEVICE9 iface,
					  LPDIRECT3DINDEXBUFFER9 pIndexData)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  LPDIRECT3DINDEXBUFFER9 olddata;

  TRACE("(%p)->(%p)\n", This, pIndexData);

  olddata = This->current->indices;

  if (pIndexData != olddata) {
    if (pIndexData) IDirect3DResource9_AddRefInternal((LPDIRECT3DRESOURCE9)pIndexData);
    This->current->indices = pIndexData;
    if (olddata) IDirect3DResource9_ReleaseInternal((LPDIRECT3DRESOURCE9)olddata);
  }
  This->current->dirty_indices = TRUE;

  if (dx8_hal) {
    /* FIXME: should emit D3DHAL_DP2SETINDICES */
  }

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_GetIndices(LPDIRECT3DDEVICE9 iface,
					  LPDIRECT3DINDEXBUFFER9* ppIndexData)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  *ppIndexData = This->state.indices;
  TRACE("(%p)->(%p)\n", This, ppIndexData);
  if (*ppIndexData) IDirect3DIndexBuffer9_AddRef(*ppIndexData);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_SetFVF(LPDIRECT3DDEVICE9 iface,
                                      DWORD FVF)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface); 
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETVERTEXSHADERDECL, 0, {1} };
  D3DHAL_DP2VERTEXSHADER data;

  TRACE("(%p)->(0x%08lx)\n", This, FVF);

  This->current->vertex_fvf = FVF;
  This->current->dirty_fvf = TRUE;
  if (This->current->vertex_decl) IDirect3DVertexDeclaration9_Release(This->current->vertex_decl);
  This->current->vertex_decl = NULL;
  This->current->dirty_vdecl = TRUE;
  if (!This->current->state_block)
    This->d3dp.vertex_fvf = FVF;

  data.dwHandle = 0; /* should actually use the FVF here */
  if (dx9_hal) {
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  } else {
    return D3D_OK;
  }
}

HRESULT WINAPI Direct3DDevice9_GetFVF(LPDIRECT3DDEVICE9 iface,
                                      DWORD *FVF)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  TRACE("(%p)->(%p)\n", This, FVF);

  /* retrieve the FVF from where SetFVF put it and assign to *FVF */
  *FVF = This->d3dp.vertex_fvf;
  return D3D_OK;
}
