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

#include "config.h"

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

#include "d3d8_private.h"

extern void WINAPI __wine_get_hal(LPDDRAWI_DIRECTDRAW_GBL *gbl, LPDDHAL_GETDRIVERINFO *ddgdi);

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

static LPDDRAWI_DIRECTDRAW_GBL dd_gbl;
static LPDDHAL_CALLBACKS dd_cbs;
static LPD3DHAL_GLOBALDRIVERDATA d3d_hal_data;
static LPD3DHAL_D3DEXTENDEDCAPS d3d_hal_extcaps;
static LPD3DHAL_CALLBACKS d3d_hal_cbs1;
static LPD3DHAL_CALLBACKS2 d3d_hal_cbs2;
static LPDDHAL_GETDRIVERINFO dd_get_driver_info = NULL;

static D3DCAPS8 *d3d8caps;

static GUID hal_guid = {0x40c1b248,0x9d7d,0x4a29,{0xd7,0xb7,0x4c,0xd8,0x10,0x9f,0x3d,0x5d}};

static INT orig_width, orig_height;
static D3DFORMAT std_format;

HANDLE buffer_heap, surface_heap;
BOOL dx8_hal;

HMODULE d3dgl;
typedef LPVOID WINAPI (*D3DGetCapsFunc)(LPDDRAWI_DIRECTDRAW_GBL, DWORD);
typedef LPVOID WINAPI (*D3DConstructFunc)(LPD3D_P, DWORD);
typedef void WINAPI (*D3DDestructFunc)(LPVOID);

struct mode { int width, height; };

static const struct mode modes[] = {
  { 512, 384 }, { 640, 400 }, { 640, 480 }, { 800, 600 }, { 1024, 768 },
  { 1152, 864 }, { 1280, 1024 }, { 1600, 1200 }
};
static const int num_modes = sizeof(modes)/sizeof(modes[0]);

static const D3DFORMAT formats[] = {
  D3DFMT_P8, D3DFMT_X1R5G5B5, D3DFMT_R5G6B5, D3DFMT_R8G8B8, D3DFMT_X8R8G8B8
};
static const int num_formats = sizeof(formats)/sizeof(formats[0]);

#define FAKE_REFRESH_RATE 60

static const char ddProp[] = "WINE_D3D8_Property";

static DWORD choose_mode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP,
			 DWORD dwRefreshRate, DWORD dwFlags)
{
  int best = -1;
  int i;

  if (!dd_gbl->dwNumModes) return 0;

/* let's support HALs that cannot switch depths (XVidMode),
 * these should return dwBPP == 0 for all their resolutions */
#define BPP_MATCH(dd, bpp) ((!(dd)) || ((dd) == bpp))

/* FIXME: we should try to match the refresh rate too */

  /* Choose the smallest mode that is large enough. */
  for (i=0; i < dd_gbl->dwNumModes; i++) {
    if (dd_gbl->lpModeInfo[i].dwWidth >= dwWidth &&
	dd_gbl->lpModeInfo[i].dwHeight >= dwHeight &&
	BPP_MATCH(dd_gbl->lpModeInfo[i].dwBPP, dwBPP)) {
      if (best == -1) best = i;
      else {
	if (dd_gbl->lpModeInfo[i].dwWidth < dd_gbl->lpModeInfo[best].dwWidth ||
	    dd_gbl->lpModeInfo[i].dwHeight < dd_gbl->lpModeInfo[best].dwHeight)
	  best = i;
      }
    }
  }

  if (best == -1) {
    TRACE("all modes too small\n");
    /* ok, let's use the largest */

    for (i=0; i < dd_gbl->dwNumModes; i++) {
      if (BPP_MATCH(dd_gbl->lpModeInfo[i].dwBPP, dwBPP)) {
	if (best == -1) best = i;
	else {
	  if (dd_gbl->lpModeInfo[i].dwWidth > dd_gbl->lpModeInfo[best].dwWidth ||
	      dd_gbl->lpModeInfo[i].dwHeight > dd_gbl->lpModeInfo[best].dwHeight)
	    best = i;
	}
      }
    }
  }
#undef BPP_MATCH

  if (best == -1) {
    ERR("requested color depth (%ld) not available, try reconfiguring X server\n", dwBPP);
    return dd_gbl->dwModeIndex;
  }

  TRACE("using mode %d\n", best);

  return best;
}

static HRESULT set_mode(DWORD dwMode)
{
  HRESULT hr = DD_OK;

  if (dwMode != dd_gbl->dwModeIndex)
  {
    DDHAL_SETMODEDATA data;
    data.lpDD = dd_gbl;
    data.dwModeIndex = dwMode;
    data.ddRVal = 0;
    data.SetMode = dd_cbs->HALDD.SetMode;
    data.inexcl = 0;
    data.useRefreshRate = FALSE;
    if (data.SetMode)
      data.SetMode(&data);
    hr = data.ddRVal;
    if (SUCCEEDED(hr))
      dd_gbl->dwModeIndex = dwMode;
  }
  return hr;
}

static void Direct3DDevice8_DestroyContext(LPDIRECT3DDEVICE8 iface);

static LRESULT WINAPI
D3D8_WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    IDirect3DDevice8Impl* This = (IDirect3DDevice8Impl*)GetPropA(hWnd, ddProp);
    LPDIRECT3DDEVICE8 iface = (LPDIRECT3DDEVICE8)This;
    TRACE("(%p)->(%08x,%x,%x,%lx)\n", iface, hWnd, iMsg, wParam, lParam);

    switch (iMsg)
    {
    case WM_SYSKEYDOWN:
    case WM_KEYDOWN:
    case WM_SYSKEYUP:
    case WM_KEYUP:
    case WM_SYSCHAR:
    case WM_CHAR:
	if (This->local.dwLocalFlags & DDRAWILCL_ISFULLSCREEN)
	{
            BOOL bMinimize = FALSE;

	    if ((lParam & (1 << 29)) && wParam)
	    {
		/* ALT */
		if (wParam == VK_TAB || wParam == VK_ESCAPE)
		    bMinimize = TRUE;
	    }
	    else
	    {
		/* CTRL */
		if (wParam == VK_ESCAPE && (GetKeyState(VK_CONTROL) < 0))
		    bMinimize = TRUE;
	    }

            if( bMinimize )
            {
                if (iMsg == WM_KEYDOWN || iMsg == WM_SYSKEYDOWN) {
                    TRACE( "Minimizing window from key combination %d 0x%lx\n", wParam, lParam );
                    ShowWindow(hWnd, SW_MINIMIZE);
                }
                TRACE( "Not passing on key combination %d 0x%lx\n", wParam, lParam );
                return 0;
            }
	}
	break;
    case WM_SHOWWINDOW:
        TRACE( "SHOWWINDOW lParam=%ld wParam=%d\n", lParam, wParam );
        if( wParam )
        {
	    /* TestCooperativeLevel should now return DEVICENOTRESET,
	     * after which the game should call Reset. Since TestCooperativeLevel
	     * checks the style itself, we don't have to do anything here. */
	    /* Good thing too, since we've probably unsubclassed the
	     * window anyway at this point (FIXME: we shouldn't) */
        }
        break;
    case WM_SIZE:
        if( wParam == SIZE_MINIMIZED )
        {
           /* Since the main d3d window is minimizing, we should exit the
            * graphics mode set by the d3d device.
            * We also strictly check that we're minimizing to 0x0 since we have bugs
            * in our code whereby we sometimes send minimize messages when we mean to
            * maximize or restore (FIXME):( 
            */
           if( LOWORD(lParam) == HIWORD(lParam) && HIWORD(lParam) == 0 )
           {
               Direct3DDevice8_DestroyContext(iface);
               /* TestCooperativeLevel should now return DEVICELOST
                * until the game window is restored. */
           }
        }
        break;
    }

    return CallWindowProcA(This->old_wndproc, hWnd, iMsg, wParam, lParam);
}

static HRESULT Direct3DDevice8_ValidatePresent(D3DPRESENT_PARAMETERS* pPresPar)
{
  TRACE(" Dimensions          : %dx%d\n",
	pPresPar->BackBufferWidth, pPresPar->BackBufferHeight);
  TRACE(" Format              : 0x%x\n", pPresPar->BackBufferFormat);
  TRACE(" BackBufferCount     : %d\n", pPresPar->BackBufferCount);
  TRACE(" MultiSampleType     : %d\n", pPresPar->MultiSampleType);
  TRACE(" SwapEffect          : %d\n", pPresPar->SwapEffect);
  TRACE(" DeviceWindow        : %08x\n", pPresPar->hDeviceWindow);
  TRACE(" Windowed            : %d\n", pPresPar->Windowed);
  TRACE(" AutoDepthStencil    : %d\n", pPresPar->EnableAutoDepthStencil);
  TRACE(" DepthStencilFormat  : 0x%x\n", pPresPar->AutoDepthStencilFormat);
  TRACE(" Flags               : %lx\n", pPresPar->Flags);
  TRACE(" RefreshRate         : %d\n", pPresPar->FullScreen_RefreshRateInHz);
  TRACE(" PresentationInterval: %d\n", pPresPar->FullScreen_PresentationInterval);

  /* we only support one back buffer */
  if (pPresPar->BackBufferCount > 1) {
    pPresPar->BackBufferCount = 1;
#if 0 /* well, some games just don't seem to listen to us */
    return D3DERR_NOTAVAILABLE;
#endif
  }

  return D3D_OK;
}

static D3DMATRIX identity_matrix = {
  {{1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1}}
};

static HRESULT Direct3DDevice8_CreateContext(LPDIRECT3DDEVICE8 iface,
					     D3DPRESENT_PARAMETERS* pPresPar)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  DDSCAPS2 caps;
  D3DFORMAT fmt;
  D3DHAL_CONTEXTCREATEDATA data;
  D3DVIEWPORT8 vp;
  unsigned u;
  HRESULT hr;
  HDC hdc;

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

  This->current = &This->state;

  if (d3d_hal_data->hwCaps.dwFlags & D3DDD_WINE_OPENGL_DEVICE) {
    D3DConstructFunc func;
    func = (LPVOID)GetProcAddress(d3dgl, "Construct");
    if (func) This->ip_ctx = func(&This->d3dp, 8);
  }

  if (pPresPar->hDeviceWindow)
    This->local.hWnd = pPresPar->hDeviceWindow;

  This->windowed = pPresPar->Windowed;
  This->auto_zbuffer = pPresPar->EnableAutoDepthStencil;

  if (!This->windowed) {
    if (dd_gbl->lpExclusiveOwner) {
      ERR("exclusive owner already set\n");
    }
    dd_gbl->lpExclusiveOwner = &This->local;
  }

  fmt = pPresPar->BackBufferFormat;
  if (!fmt) fmt = std_format;
  TRACE("using format %d\n", fmt);

  /* save gamma */
  hdc = GetDC(This->local.hWnd);
  if (!GetDeviceGammaRamp(hdc, (LPVOID)&This->orig_gamma)) {
    for (u=0; u<256; u++) {
      This->orig_gamma.red[u]   = (u << 8) | u;
      This->orig_gamma.green[u] = (u << 8) | u;
      This->orig_gamma.blue[u]  = (u << 8) | u;
    }
  }
  ReleaseDC(This->local.hWnd, hdc);

  memset(&caps, 0, sizeof(caps));
  caps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_LOCALVIDMEM |
    DDSCAPS_VIDEOMEMORY | DDSCAPS_VISIBLE | DDSCAPS_FRONTBUFFER |
    DDSCAPS_3DDEVICE;
  if (This->windowed) {
    RECT client;
    GetClientRect(This->local.hWnd, &client);

    /* windowed */
    This->mode.Width  = GetSystemMetrics(SM_CXSCREEN);
    This->mode.Height = GetSystemMetrics(SM_CYSCREEN);
    This->mode.Format = std_format;
    This->mode.RefreshRate = FAKE_REFRESH_RATE;

    if (!pPresPar->BackBufferWidth)
      pPresPar->BackBufferWidth = client.right - client.left;
    if (!pPresPar->BackBufferHeight)
      pPresPar->BackBufferHeight = client.bottom - client.top;
  }
  else {
    /* full-screen */
    DDPIXELFORMAT fmt;
    RECT rect;
    DWORD style;
    DWORD dwMode;

    D3D8_GetPixelFormat(&fmt, pPresPar->BackBufferFormat);

    dwMode = choose_mode(pPresPar->BackBufferWidth,
			 pPresPar->BackBufferHeight,
			 fmt.u1.dwRGBBitCount,
			 pPresPar->FullScreen_RefreshRateInHz,
			 0);

    /* set display mode */
    if (!(dd_gbl->dwFlags & DDRAWI_MODECHANGED))
      dd_gbl->dwModeIndexOrig = dd_gbl->dwModeIndex;
    if (SUCCEEDED(set_mode(dwMode))) {
      dd_gbl->dwFlags |= DDRAWI_MODECHANGED;
      dd_gbl->dwModeIndex = dwMode;
    }

    if (dd_gbl->dwNumModes) {
      This->mode.Width = dd_gbl->lpModeInfo[dwMode].dwWidth;
      This->mode.Height = dd_gbl->lpModeInfo[dwMode].dwHeight;
      This->mode.Format = pPresPar->BackBufferFormat;
      This->mode.RefreshRate = dd_gbl->lpModeInfo[dwMode].wRefreshRate ?
			       dd_gbl->lpModeInfo[dwMode].wRefreshRate :
			       FAKE_REFRESH_RATE;
    }
    else {
      /* fake it */
      This->mode.Width = pPresPar->BackBufferWidth;
      This->mode.Height = pPresPar->BackBufferHeight;
      This->mode.Format = pPresPar->BackBufferFormat;
      This->mode.RefreshRate = FAKE_REFRESH_RATE;
    }

    /* subclass device window */
    SetPropA(This->local.hWnd, ddProp, (LONG)This);
    This->old_wndproc = (WNDPROC)SetWindowLongA(This->local.hWnd,
						GWL_WNDPROC,
						(LONG)D3D8_WndProc);

    /* remove device window decorations */
    style = GetWindowLongA(This->local.hWnd, GWL_STYLE);
    This->old_style = style;
    style &= ~WS_OVERLAPPEDWINDOW;
    style |= WS_POPUP;

    /* FIXME: windows still renders the d3d even when the window is hidden.
     * this is because it draws directly to the primary buffer, and its
     * allowed to hide the window whilest still displaying graphics.
     * This is a temporary hack for GTA3 that seems to hide its window.
     * A better solution may be to create a seperate window for the rendering
     * or something similar.
     * Tests done: Create a simply d3d8 app on win32, hid the window and let it
     * redraw - the window borders and all were gone but the d3d8 stuff still
     * was visible in its normal place.
     * Also, this wont fix cases where the window is hidden after the context
     * has been created.
     */
    style |= WS_VISIBLE;

    SetWindowLongA(This->local.hWnd, GWL_STYLE, style);

    /* resize device window */
    rect.left = 0;
    rect.top = 0;
    rect.right = This->mode.Width;
    rect.bottom = This->mode.Height;
    AdjustWindowRectEx(&rect,
		       GetWindowLongA(This->local.hWnd, GWL_STYLE),
		       FALSE,
		       GetWindowLongA(This->local.hWnd, GWL_EXSTYLE));
    SetWindowPos(This->local.hWnd, 0,
		 rect.left, rect.right,
		 rect.right - rect.left,
		 rect.bottom - rect.top,
		 SWP_NOMOVE | SWP_NOZORDER);

    /* create primary surface */
    hr = Direct3DDevice8_CreateSurface(iface, (LPUNKNOWN)iface,
				       pPresPar->BackBufferWidth,
				       pPresPar->BackBufferHeight,
				       pPresPar->BackBufferFormat,
				       &caps,
				       &This->primary,
				       TRUE);
    if (FAILED(hr)) goto failed;
    ((IDirect3DSurface8Impl*)This->primary)->t.more.lpDDRAWReserved =
      (LPVOID)This->local.hWnd;
  }

  if (This->windowed)
    caps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_LOCALVIDMEM |
      DDSCAPS_VIDEOMEMORY | DDSCAPS_3DDEVICE;
  else
    caps.dwCaps = DDSCAPS_BACKBUFFER | DDSCAPS_LOCALVIDMEM |
      DDSCAPS_VIDEOMEMORY | DDSCAPS_3DDEVICE;
  hr = Direct3DDevice8_CreateSurface(iface, (LPUNKNOWN)iface,
				     pPresPar->BackBufferWidth,
				     pPresPar->BackBufferHeight,
				     fmt, &caps,
				     &This->backbuf,
				     TRUE);
  if (FAILED(hr)) goto failed;
  if (This->primary)
    Direct3DDevice8_AttachSurface(This->primary, This->backbuf);
  else
    ((IDirect3DSurface8Impl*)This->backbuf)->t.more.lpDDRAWReserved =
      (LPVOID)pPresPar->hDeviceWindow;

  if (This->windowed) {
    This->mem_dc = CreateCompatibleDC(0);
    This->old_bmp =
      SelectObject(This->mem_dc,
		   ((IDirect3DSurface8Impl*)This->backbuf)->t.gbl_more.hKernelSurface);
  }

  IDirect3DSurface8_AddRef(This->backbuf);
  This->target = This->backbuf;
  This->d3dp.surf = &((IDirect3DSurface8Impl*)This->target)->t.lcl;

  if (This->auto_zbuffer) {
    caps.dwCaps = DDSCAPS_ZBUFFER;
    hr = Direct3DDevice8_CreateSurface(iface, (LPUNKNOWN)iface,
				       pPresPar->BackBufferWidth,
				       pPresPar->BackBufferHeight,
				       pPresPar->AutoDepthStencilFormat,
				       &caps,
				       &This->zbuffer,
				       TRUE);
    if (FAILED(hr)) goto failed;
    Direct3DDevice8_AttachSurface(This->target, This->zbuffer);

    IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)This->zbuffer);
    This->ztarget = This->zbuffer;
    This->d3dp.zbuf = &((IDirect3DSurface8Impl*)This->ztarget)->t.lcl;
  }

  /* create 3D context */
  data.u1.lpDDLcl = &This->local;
  data.u2.lpDDSLcl = This->d3dp.surf;
  data.u3.lpDDSZLcl = This->d3dp.zbuf;
  data.u4.dwPID = This->local.dwProcessId;
  data.ddrval = 0;
  data.dwhContext = 0;
  This->d3dp.cbs1->ContextCreate(&data);
  hr = data.ddrval;
  if (FAILED(hr)) goto failed;
  This->d3dp.dwhContext = data.dwhContext;

  /* initialize render states */
  TRACE("initializing default render states\n");

  vp.X = 0;
  vp.Y = 0;
  vp.Width = pPresPar->BackBufferWidth;
  vp.Height = pPresPar->BackBufferHeight;
  vp.MinZ = 0;
  vp.MaxZ = 1;
  IDirect3DDevice8_SetViewport(iface, &vp);

  IDirect3DDevice8_SetTransform(iface, D3DTS_WORLD,
				&identity_matrix);
  IDirect3DDevice8_SetTransform(iface, D3DTS_VIEW,
				&identity_matrix);
  IDirect3DDevice8_SetTransform(iface, D3DTS_PROJECTION,
				&identity_matrix);

  /* haven't bothered to call SetRenderState for any defaults FALSE or 0 */

  if (This->zbuffer)
    IDirect3DDevice8_SetRenderState(iface, D3DRS_ZENABLE, D3DZB_TRUE);

  IDirect3DDevice8_SetRenderState(iface, D3DRS_FILLMODE, D3DFILL_SOLID);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_ZWRITEENABLE, TRUE);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_LASTPIXEL, TRUE);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_SRCBLEND, D3DBLEND_ONE);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_DESTBLEND, D3DBLEND_ZERO);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_CULLMODE, D3DCULL_CCW);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_ALPHAFUNC, D3DCMP_ALWAYS);

  IDirect3DDevice8_SetRenderState(iface, D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_STENCILMASK, 0xffffffff);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_STENCILWRITEMASK, 0xffffffff);

  /* FIXME: texturefactor is 0xffffffff? is this also the case in d3d7? */

  IDirect3DDevice8_SetRenderState(iface, D3DRS_CLIPPING, TRUE);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_LIGHTING, TRUE);

  IDirect3DDevice8_SetRenderState(iface, D3DRS_COLORVERTEX, TRUE);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_LOCALVIEWER, TRUE);

  IDirect3DDevice8_SetRenderState(iface, D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_SPECULARMATERIALSOURCE, D3DMCS_COLOR2);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR2);

  /* FIXME: D3DRS_POINTSIZE     = 1.0f */
  /* FIXME: D3DRS_POINTSIZE_MIN = 0.0f */
  /* FIXME: D3DRS_POINTSCALE_A  = 1.0f */
  /* FIXME: D3DRS_POINTSCALE_B  = 0.0f */
  /* FIXME: D3DRS_POINTSCALE_C  = 0.0f */

  IDirect3DDevice8_SetRenderState(iface, D3DRS_MULTISAMPLEANTIALIAS, TRUE);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_MULTISAMPLEMASK, 0xffffffff);

  /* FIXME: D3DRS_PATCHSEGMENTS = 1.0f */
  /* FIXME: D3DRS_POINTSIZE_MAX = 64.0f */

  IDirect3DDevice8_SetRenderState(iface, D3DRS_COLORWRITEENABLE, 0xf);

  /* FIXME: D3DRS_TWEENFACTOR = 0.0f */

  IDirect3DDevice8_SetRenderState(iface, D3DRS_BLENDOP, D3DBLENDOP_ADD);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_POSITIONORDER, D3DORDER_CUBIC);
  IDirect3DDevice8_SetRenderState(iface, D3DRS_NORMALORDER, D3DORDER_LINEAR);

  IDirect3DDevice8_SetTextureStageState(iface, 0, D3DTSS_COLOROP, D3DTOP_MODULATE);
  IDirect3DDevice8_SetTextureStageState(iface, 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

  for (u=0; u<d3d_hal_extcaps->wMaxTextureBlendStages; u++) {
    IDirect3DDevice8_SetTransform(iface, D3DTS_TEXTURE0+u, &identity_matrix);

    if( u>0 ) {
      IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_COLOROP, D3DTOP_DISABLE);
      IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
    }
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_COLORARG0, D3DTA_CURRENT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_COLORARG2, D3DTA_CURRENT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ALPHAARG0, D3DTA_CURRENT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_RESULTARG, D3DTA_CURRENT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_TEXCOORDINDEX, u);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_ADDRESSW, D3DTADDRESS_WRAP);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_MAGFILTER, D3DTEXF_POINT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_MINFILTER, D3DTEXF_POINT);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_MIPFILTER, D3DTEXF_NONE);
    IDirect3DDevice8_SetTextureStageState(iface, u, D3DTSS_MAXANISOTROPY, 1);
  }

  return D3D_OK;

failed:
  Direct3DDevice8_DestroyContext(iface);
  return hr;
}

static void Direct3DDevice8_DestroyContext(LPDIRECT3DDEVICE8 iface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_CONTEXTDESTROYDATA data;
  unsigned u;
  HDC hdc;

  IDirect3DBaseTexture8Impl *current_texture;
  VertexShader *current_vs;

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

  if (This->d3dp.in_scene) {
    ERR("destroying context while in scene\n");
    IDirect3DDevice8_EndScene(iface);
  }

  /* unload textures */
  current_texture = This->textures;
  while (current_texture)
  {
    current_texture->UnloadTexture(current_texture);
    current_texture = current_texture->next_device;
  }

  /* clear render states */
  HeapFree(GetProcessHeap(), 0, This->d3dp.light);
  for (u=0; u<8; u++) {
    if (This->state.tex[u]) {
      IDirect3DBaseTexture8_Release(This->state.tex[u]);
      This->state.tex[u] = NULL;
    }
  }
  This->d3dp.light = NULL;
  This->d3dp.numlights = 0;
#define CLEAR(x) memset(&x, 0, sizeof(x))
  CLEAR(This->d3dp.renderstate);
  CLEAR(This->d3dp.texstagestate);
  CLEAR(This->d3dp.xfrm);
  CLEAR(This->d3dp.txfrm);
  CLEAR(This->d3dp.viewport);
  CLEAR(This->d3dp.material);
#undef CLEAR

  Direct3DDevice8_DestroyPalettes(iface);

  /* destroy 3D context */
  data.dwhContext = This->d3dp.dwhContext;
  data.ddrval = 0;
  This->d3dp.cbs1->ContextDestroy(&data);

  /* traverse list of textures and null any context's
   * that are the one we are destroying.
   */
  current_texture = This->textures;
  while (current_texture)
  {
    IDirect3DSurface8Impl *surface = (IDirect3DSurface8Impl *)current_texture->main;
    if (surface->t.lcl.dwReserved1 == This->d3dp.dwhContext)
        surface->t.lcl.dwReserved1 = 0;
    current_texture = current_texture->next_device;
  }

  /* traverse list of vertex shaders and null any contexts
   * that are the one we are destroying */
  current_vs = This->vshaders;
  while (current_vs) {
    if (current_vs->DriverReserved1 == This->d3dp.dwhContext) {
      current_vs->DriverReserved1 = 0;
    }
    current_vs = current_vs->next;
  }

  This->d3dp.dwhContext = 0;

  if (This->windowed) {
    SelectObject(This->mem_dc, This->old_bmp);
    DeleteDC(This->mem_dc);
    This->mem_dc = 0;
  }

  if (This->target) {
    IDirect3DSurface8_Release(This->target);
    This->target = NULL;
    This->d3dp.surf = NULL;
  }
  if (This->ztarget) {
    IDirect3DSurface8_Release(This->ztarget);
    This->ztarget = NULL;
    This->d3dp.zbuf = NULL;
  }
  if (This->zbuffer) {
    IDirect3DSurface8_Release(This->zbuffer);
    This->zbuffer = NULL;
  }
  if (This->backbuf) {
    IDirect3DSurface8_Release(This->backbuf);
    This->backbuf = NULL;
  }
  if (This->primary) {
    IDirect3DSurface8_Release(This->primary);
    This->primary = NULL;
  }

  /* restore display mode */
  if (dd_gbl->dwFlags & DDRAWI_MODECHANGED) {
    set_mode(dd_gbl->dwModeIndexOrig);
    dd_gbl->dwModeIndex = dd_gbl->dwModeIndexOrig;
    dd_gbl->dwFlags &= ~DDRAWI_MODECHANGED;
  }

  if (!This->windowed) {
    /* restore device window decorations */
    /* the d3d8 samples seem to do this themselves before calling us,
     * so let's only do this if that didn't happen before CreateContext */
    if (!(This->old_style & WS_POPUP))
      SetWindowLongA(This->local.hWnd, GWL_STYLE, This->old_style);
    /* unsubclass window */
    SetWindowLongA(This->local.hWnd, GWL_WNDPROC, (LONG)This->old_wndproc);
    RemovePropA(This->local.hWnd, ddProp);
  }

  if ((d3d_hal_data->hwCaps.dwFlags & D3DDD_WINE_OPENGL_DEVICE) &&
      (This->ip_ctx)) {
    D3DDestructFunc func;
    func = (LPVOID)GetProcAddress(d3dgl, "Destruct");
    if (func) func(This->ip_ctx);
  }

  /* restore gamma */
  hdc = GetDC(This->local.hWnd);
  SetDeviceGammaRamp(hdc, (LPVOID)&This->orig_gamma);
  ReleaseDC(This->local.hWnd, hdc);

  if (dd_gbl->lpExclusiveOwner == &This->local)
    dd_gbl->lpExclusiveOwner = NULL;
}

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

ULONG WINAPI Direct3DDevice8_AddRef(LPDIRECT3DDEVICE8 iface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref;
}

ULONG WINAPI Direct3DDevice8_Release(LPDIRECT3DDEVICE8 iface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);

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

  Direct3DDevice8_DestroyContext(iface);
  IDirect3D8_Release((IDirect3D8*)This->parent);

  if (This->d3dp.vertex_const) HeapFree(GetProcessHeap(), 0, This->d3dp.vertex_const);
  HeapFree(GetProcessHeap(), 0, This);
  return 0;
}

HRESULT WINAPI Direct3DDevice8_TestCooperativeLevel(LPDIRECT3DDEVICE8 iface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  DWORD style;
  TRACE("(%p)->()\n", iface);
  if (This->backbuf) return D3D_OK;
  style = GetWindowLongA(This->local.hWnd, GWL_STYLE);
  if (style & WS_MINIMIZE) return D3DERR_DEVICELOST;
  return D3DERR_DEVICENOTRESET;
}

UINT WINAPI Direct3DDevice8_GetAvailableTextureMem(LPDIRECT3DDEVICE8 iface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->()\n", iface);
  return This->local.lpGbl->ddCaps.dwVidMemFree;
}

HRESULT WINAPI Direct3DDevice8_ResourceManagerDiscardBytes(LPDIRECT3DDEVICE8 iface,
							   DWORD Bytes)
{
  FIXME("(%p)->(%ld): stub\n", iface, Bytes);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetDirect3D(LPDIRECT3DDEVICE8 iface,
					   IDirect3D8** ppD3D8)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  IDirect3D8* pD3D8;
  TRACE("(%p)->(%p)\n", iface, ppD3D8);

  pD3D8 = (IDirect3D8*)This->parent;
  IDirect3D8_AddRef(pD3D8);
  *ppD3D8 = pD3D8;

  TRACE("=> %p\n", *ppD3D8);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetDeviceCaps(LPDIRECT3DDEVICE8 iface,
					     D3DCAPS8* pCaps)
{
  TRACE("(%p)->(%p)\n", iface, pCaps);
  memcpy(pCaps, d3d8caps, sizeof(D3DCAPS8));
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetDisplayMode(LPDIRECT3DDEVICE8 iface,
					      D3DDISPLAYMODE* pMode)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p}->(%p)\n", iface, pMode);
  *pMode = This->mode;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetCreationParameters(LPDIRECT3DDEVICE8 iface,
						     D3DDEVICE_CREATION_PARAMETERS* pParms)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p}->(%p)\n", iface, pParms);
  pParms->AdapterOrdinal = 0;
  pParms->DeviceType = D3DDEVTYPE_HAL;
  pParms->hFocusWindow = This->local.hFocusWnd;
  pParms->BehaviorFlags = This->behavior;
/*
  pParms->BehaviorFlags = 0;
  if (This->local.dwLocalFlags & DDRAWILCL_FPUPRESERVE)
    pParms->BehaviorFlags |= D3DCREATE_FPU_PRESERVE;
  if (This->local.dwLocalFlags & DDRAWILCL_MULTITHREADED)
    pParms->BehaviorFlags |= D3DCREATE_MULTITHREADED;
*/
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_Reset(LPDIRECT3DDEVICE8 iface,
				     D3DPRESENT_PARAMETERS* pPresPar)
{
  HRESULT hr;
  TRACE("(%p)->(%p)\n", iface, pPresPar);
  Direct3DDevice8_DestroyContext(iface);
  hr = Direct3DDevice8_ValidatePresent(pPresPar);
  if (FAILED(hr)) return hr;
  return Direct3DDevice8_CreateContext(iface, pPresPar);
}

HRESULT WINAPI Direct3DDevice8_GetBackBuffer(LPDIRECT3DDEVICE8 iface,
					     UINT BackBuffer,
					     D3DBACKBUFFER_TYPE Type,
					     IDirect3DSurface8** ppSurface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%d,%d,%p)\n", iface, BackBuffer, Type, ppSurface);

  *ppSurface = NULL;

  if (!This->backbuf)
    return D3DERR_INVALIDCALL;

  if (BackBuffer > 0)
    return D3DERR_INVALIDCALL;

  if (Type != D3DBACKBUFFER_TYPE_MONO)
    return D3DERR_INVALIDCALL;

  IDirect3DSurface8_AddRef(This->backbuf);
  *ppSurface = This->backbuf;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetFrontBuffer(LPDIRECT3DDEVICE8 iface,
					      IDirect3DSurface8* pDestSurface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  IDirect3DSurface8Impl* primary;
  IDirect3DSurface8Impl* dest;
  HDC mem_dc;
  HBITMAP bmp, old_bmp;
  BITMAPINFOHEADER info;

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

  if (!This->primary)
    return D3DERR_INVALIDCALL;

  primary = (IDirect3DSurface8Impl*)This->primary;
  bmp = primary->t.gbl_more.hKernelSurface;

  dest = (IDirect3DSurface8Impl*)pDestSurface;

  /* The docs say that this method converts to D3DFMT_A8R8G8B8 format */
  /* FIXME: we should check that the dest surface is OK */

  memset(&info, 0, sizeof(info));
  info.biSize = sizeof(info);
  info.biWidth = dest->t.gbl.wWidth;
  info.biHeight = -dest->t.gbl.wHeight;
  info.biPlanes = 1;
  info.biBitCount = dest->t.gbl.ddpfSurface.u1.dwRGBBitCount;

  mem_dc = CreateCompatibleDC(0);
  old_bmp = SelectObject(mem_dc, bmp);
  GetDIBits(mem_dc, bmp, 0, dest->t.gbl.wHeight,
	    (LPVOID)dest->t.gbl.fpVidMem,
	    (BITMAPINFO*)&info, DIB_RGB_COLORS);
  SelectObject(mem_dc, old_bmp);
  DeleteDC(mem_dc);

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetRenderTarget(LPDIRECT3DDEVICE8 iface,
					       IDirect3DSurface8* pRenderTarget,
					       IDirect3DSurface8* pNewZStencil)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  IDirect3DSurface8* oldTarget = NULL;
  IDirect3DSurface8* oldZStencil = NULL;
  D3DHAL_SETRENDERTARGETDATA data;
  IDirect3DSurface8Impl* newTarget = (IDirect3DSurface8Impl*)pRenderTarget;
  IDirect3DSurface8Impl* newZStencil = (IDirect3DSurface8Impl*)pNewZStencil;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_VIEWPORTINFO, 0, {1} };
  D3DHAL_DP2VIEWPORTINFO view;

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

#if 0
  /* render target must have been created with D3DUSAGE_RENDERTARGET */
  if (newTarget && !(newTarget->t.lcl.ddsCaps.dwCaps & DDSCAPS_3DDEVICE))
    return D3DERR_INVALIDCALL;

  /* z/stencil buffer must have been created with D3DUSAGE_DEPTHSTENCIL */
  if (newZStencil && !(newZStencil->t.lcl.ddsCaps.dwCaps & DDSCAPS_ZBUFFER))
    return D3DERR_INVALIDCALL;
#endif

  if (pRenderTarget) {
    oldTarget = This->target;
    IDirect3DSurface8_AddRef(pRenderTarget);
    This->target = pRenderTarget;
    This->d3dp.surf = &newTarget->t.lcl;
  }

  oldZStencil = This->ztarget;
  if (pNewZStencil) IDirect3DSurface8_AddRef(pNewZStencil);
  This->ztarget = pNewZStencil;
  This->d3dp.zbuf = This->ztarget ? &newZStencil->t.lcl : NULL;

  data.dwhContext = This->d3dp.dwhContext;
  data.u1.lpDDSLcl = This->d3dp.surf;
  data.u2.lpDDSZLcl = This->d3dp.zbuf;
  data.ddrval = 0;
  This->d3dp.cbs2->SetRenderTarget(&data);

  if (oldTarget) IDirect3DSurface8_Release(oldTarget);
  if (oldZStencil) IDirect3DSurface8_Release(oldZStencil);

  if (This->ztarget) {
    if (This->d3dp.renderstate[D3DRENDERSTATE_ZENABLE] == D3DZB_FALSE)
      IDirect3DDevice8_SetRenderState(iface, D3DRENDERSTATE_ZENABLE, D3DZB_TRUE);
  }
  else {
    if (This->d3dp.renderstate[D3DRENDERSTATE_ZENABLE] != D3DZB_FALSE)
      IDirect3DDevice8_SetRenderState(iface, D3DRENDERSTATE_ZENABLE, D3DZB_FALSE);
  }

  /* change viewport */
  This->d3dp.viewport.dwX = 0;
  This->d3dp.viewport.dwY = 0;
  This->d3dp.viewport.dwWidth = This->d3dp.surf->lpGbl->wWidth;
  This->d3dp.viewport.dwHeight = This->d3dp.surf->lpGbl->wHeight;

  view.dwX = This->d3dp.viewport.dwX;
  view.dwY = This->d3dp.viewport.dwY;
  view.dwWidth = This->d3dp.viewport.dwWidth;
  view.dwHeight = This->d3dp.viewport.dwHeight;

  This->emit_dp2(This->ip_ctx, &cmd, &view, sizeof(view), NULL, 0);

  TRACE("render target changed\n");

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetRenderTarget(LPDIRECT3DDEVICE8 iface,
					       IDirect3DSurface8** ppSurface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%p)\n", iface, ppSurface);

  if (!This->backbuf) {
    *ppSurface = NULL;
    return D3DERR_INVALIDCALL;
  }

  IDirect3DSurface8_AddRef(This->backbuf);
  *ppSurface = This->backbuf;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetDepthStencilSurface(LPDIRECT3DDEVICE8 iface,
						      IDirect3DSurface8** ppSurface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%p)\n", iface, ppSurface);

  if (!This->ztarget) {
    *ppSurface = NULL;
    return D3DERR_INVALIDCALL;
  }

  IDirect3DSurface8_AddRef(This->ztarget);
  *ppSurface = This->ztarget;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetInfo(LPDIRECT3DDEVICE8 iface,
				       DWORD DevInfoID,
				       void* pDevInfoStruct,
				       DWORD DevInfoStructSize)
{
  TRACE("(%p)->(%ld,%p,%ld)\n", iface,
	DevInfoID, pDevInfoStruct, DevInfoStructSize);
  /* on retail builds of DirectX, this method returns S_FALSE */
  return S_FALSE;
}

static ICOM_VTABLE(IDirect3DDevice8) D3DD8_Vtbl =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DDevice8_QueryInterface,
  Direct3DDevice8_AddRef,
  Direct3DDevice8_Release,
  Direct3DDevice8_TestCooperativeLevel,
  Direct3DDevice8_GetAvailableTextureMem,
  Direct3DDevice8_ResourceManagerDiscardBytes,
  Direct3DDevice8_GetDirect3D,
  Direct3DDevice8_GetDeviceCaps,
  Direct3DDevice8_GetDisplayMode,
  Direct3DDevice8_GetCreationParameters,
  Direct3DDevice8_SetCursorProperties,
  Direct3DDevice8_SetCursorPosition,
  Direct3DDevice8_ShowCursor,
  (void*)0xdead100f, /* CreateAdditionalSwapChain */
  Direct3DDevice8_Reset,
  Direct3DDevice8_Present,
  Direct3DDevice8_GetBackBuffer,
  (void*)0xdead1013, /* GetRasterStatus */
  Direct3DDevice8_SetGammaRamp,
  Direct3DDevice8_GetGammaRamp,
  Direct3DDevice8_CreateTexture,
  (void*)0xdead1017, /* CreateVolumeTexture */
  Direct3DDevice8_CreateCubeTexture,
  Direct3DDevice8_CreateVertexBuffer,
  Direct3DDevice8_CreateIndexBuffer,
  Direct3DDevice8_CreateRenderTarget,
  Direct3DDevice8_CreateDepthStencilSurface,
  Direct3DDevice8_CreateImageSurface,
  Direct3DDevice8_CopyRects,
  Direct3DDevice8_UpdateTexture,
  Direct3DDevice8_GetFrontBuffer,
  Direct3DDevice8_SetRenderTarget,
  Direct3DDevice8_GetRenderTarget,
  Direct3DDevice8_GetDepthStencilSurface,
  Direct3DDevice8_BeginScene,
  Direct3DDevice8_EndScene,
  Direct3DDevice8_Clear,
  Direct3DDevice8_SetTransform,
  Direct3DDevice8_GetTransform,
  (void*)0xdead102b, /* MultiplyTransform */
  Direct3DDevice8_SetViewport,
  Direct3DDevice8_GetViewport,
  Direct3DDevice8_SetMaterial,
  Direct3DDevice8_GetMaterial,
  Direct3DDevice8_SetLight,
  Direct3DDevice8_GetLight,
  Direct3DDevice8_LightEnable,
  Direct3DDevice8_GetLightEnable,
  Direct3DDevice8_SetClipPlane,
  Direct3DDevice8_GetClipPlane,
  Direct3DDevice8_SetRenderState,
  Direct3DDevice8_GetRenderState,
  Direct3DDevice8_BeginStateBlock,
  Direct3DDevice8_EndStateBlock,
  Direct3DDevice8_ApplyStateBlock,
  Direct3DDevice8_CaptureStateBlock,
  Direct3DDevice8_DeleteStateBlock,
  Direct3DDevice8_CreateStateBlock,
  (void*)0xdead103f, /* SetClipStatus */
  (void*)0xdead1040, /* GetClipStatus */
  Direct3DDevice8_GetTexture,
  Direct3DDevice8_SetTexture,
  Direct3DDevice8_GetTextureStageState,
  Direct3DDevice8_SetTextureStageState,
  Direct3DDevice8_ValidateDevice,
  Direct3DDevice8_GetInfo,
  Direct3DDevice8_SetPaletteEntries,
  Direct3DDevice8_GetPaletteEntries,
  Direct3DDevice8_SetCurrentTexturePalette,
  Direct3DDevice8_GetCurrentTexturePalette,
  Direct3DDevice8_DrawPrimitive,
  Direct3DDevice8_DrawIndexedPrimitive,
  Direct3DDevice8_DrawPrimitiveUP,
  Direct3DDevice8_DrawIndexedPrimitiveUP,
  (void*)0xdead1050, /* ProcessVertices */
  Direct3DDevice8_CreateVertexShader,
  Direct3DDevice8_SetVertexShader,
  Direct3DDevice8_GetVertexShader,
  Direct3DDevice8_DeleteVertexShader,
  Direct3DDevice8_SetVertexShaderConstant,
  Direct3DDevice8_GetVertexShaderConstant,
  Direct3DDevice8_GetVertexShaderDeclaration,
  Direct3DDevice8_GetVertexShaderFunction,
  Direct3DDevice8_SetStreamSource,
  Direct3DDevice8_GetStreamSource,
  Direct3DDevice8_SetIndices,
  Direct3DDevice8_GetIndices,
  (void*)0xdead105e, /* CreatePixelShader */
  Direct3DDevice8_SetPixelShader,
  Direct3DDevice8_GetPixelShader,
  (void*)0xdead1061, /* DeletePixelShader */
  Direct3DDevice8_SetPixelShaderConstant,
  (void*)0xdead1063, /* GetPixelShaderConstant */
  (void*)0xdead1064, /* GetPixelShaderFunction */
  (void*)0xdead1065, /* DrawRectPatch */
  (void*)0xdead1066, /* DrawTriPatch */
  (void*)0xdead1067  /* DeletePatch */
};

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

ULONG WINAPI Direct3D8_AddRef(LPDIRECT3D8 iface)
{
  ICOM_THIS(IDirect3D8Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref;
}

ULONG WINAPI Direct3D8_Release(LPDIRECT3D8 iface)
{
  ICOM_THIS(IDirect3D8Impl, iface);
  TRACE("(%p)->()\n", iface);
  if (--This->ref) return This->ref;

  HeapFree(GetProcessHeap(), 0, This);
  return 0;
}

#define NUMBER_OF_ADAPTERS (dd_gbl ? 1 : 0)

UINT WINAPI Direct3D8_GetAdapterCount(LPDIRECT3D8 iface)
{
  TRACE("(%p}->()\n", iface);
  return NUMBER_OF_ADAPTERS;
}

HRESULT WINAPI Direct3D8_GetAdapterIdentifier(LPDIRECT3D8 iface,
					      UINT Adapter,
					      DWORD Flags,
					      D3DADAPTER_IDENTIFIER8* pIdentifier)
{
  TRACE("(%p}->(%d,%lx,%p)\n", iface, Adapter, Flags, pIdentifier);

  if( ( NUMBER_OF_ADAPTERS == 0 ) || ( Adapter > NUMBER_OF_ADAPTERS )
      || !pIdentifier )
  {
    if( pIdentifier ) memset( pIdentifier, 0, sizeof( *pIdentifier ) );
    return D3DERR_INVALIDCALL;
  }

  strcpy(pIdentifier->Driver, "display");
  strcpy(pIdentifier->Description, "Direct3D HAL");
  pIdentifier->DriverVersion.s.HighPart = MAKEWORD( 0xa, 0 );   /* Claim to be nvidia 44.96 drivers*/
  pIdentifier->DriverVersion.s.LowPart = MAKEWORD( 96, 44 );
  pIdentifier->VendorId = 0x600e; /* Fudge NVidia I think */
  pIdentifier->DeviceId = 0x10de;  /* GeForce 3 I think */
  pIdentifier->SubSysId = 0;
  pIdentifier->Revision = 0;
  memcpy(&pIdentifier->DeviceIdentifier, &hal_guid, sizeof(GUID));
  pIdentifier->WHQLLevel = 1; /* pretend we're certified */

  return D3D_OK;
}

UINT WINAPI Direct3D8_GetAdapterModeCount(LPDIRECT3D8 iface,
					  UINT Adapter)
{
  int i, c = 0;

  TRACE("(%p}->(%d)\n", iface, Adapter);

  for (i=0; i<num_modes; i++) {
    if (modes[i].width > orig_width || modes[i].height > orig_height)
      continue;
    c += num_formats;
  }

  return c;
}

HRESULT WINAPI Direct3D8_EnumAdapterModes(LPDIRECT3D8 iface,
					  UINT Adapter,
					  UINT Mode,
					  D3DDISPLAYMODE* pMode)
{
  int i;

  TRACE("(%p}->(%d,%d,%p)\n", iface, Adapter, Mode, pMode);

  if (Mode >= Direct3D8_GetAdapterModeCount(iface, Adapter))
	  return D3DERR_INVALIDCALL;
  i = Mode / num_formats;

  pMode->Width  = modes[i].width;
  pMode->Height = modes[i].height;
  pMode->Format = formats[Mode % num_formats];
  pMode->RefreshRate = FAKE_REFRESH_RATE;

  return D3D_OK;
}

HRESULT WINAPI Direct3D8_GetAdapterDisplayMode(LPDIRECT3D8 iface,
					       UINT Adapter,
					       D3DDISPLAYMODE* pMode)
{
  TRACE("(%p}->(%d,%p)\n", iface, Adapter, pMode);

  pMode->Width  = GetSystemMetrics(SM_CXSCREEN);
  pMode->Height = GetSystemMetrics(SM_CYSCREEN);
  pMode->Format = std_format;
  pMode->RefreshRate = FAKE_REFRESH_RATE;

  return D3D_OK;
}

HRESULT WINAPI Direct3D8_CheckDeviceType(LPDIRECT3D8 iface,
					 UINT Adapter,
					 D3DDEVTYPE CheckType,
					 D3DFORMAT DisplayFormat,
					 D3DFORMAT BackBufferFormat,
					 BOOL Windowed)
{
  TRACE("(%p}->(%d,%d,%d,%d,%d)\n", iface,
	Adapter, CheckType, DisplayFormat, BackBufferFormat, Windowed);

  /* pretend we can support anything */
  return D3D_OK;
}

HRESULT WINAPI Direct3D8_CheckDeviceFormat(LPDIRECT3D8 iface,
					   UINT Adapter,
					   D3DDEVTYPE DeviceType,
					   D3DFORMAT AdapterFormat,
					   DWORD Usage,
					   D3DRESOURCETYPE RType,
					   D3DFORMAT CheckFormat)
{
  TRACE("(%p}->(%d,%d,%d,%ld,%d,0x%x)\n", iface,
	Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat);

  switch (RType) {
  case D3DRTYPE_SURFACE:
    /* support any kind of surface */
    return D3D_OK;
  case D3DRTYPE_CUBETEXTURE:
    if (!(dd_gbl->ddsCapsMore.dwCaps2 & DDSCAPS2_CUBEMAP)) break;
    /* fall through */
  case D3DRTYPE_TEXTURE:
    {
      DDPIXELFORMAT fmt;
      DWORD c;
      D3D8_GetPixelFormat(&fmt, CheckFormat);
      if (fmt.dwFlags & DDPF_FOURCC) {
	for (c=0; c<d3d_hal_data->dwNumTextureFormats; c++) {
	  LPDDPIXELFORMAT pfmt = &d3d_hal_data->lpTextureFormats[c].ddpfPixelFormat;
	  if ((pfmt->dwFlags & DDPF_FOURCC) &&
	      (pfmt->dwFourCC == fmt.dwFourCC)) return D3D_OK;
	}
      }
      else {
	for (c=0; c<d3d_hal_data->dwNumTextureFormats; c++)
	  if (!memcmp(&d3d_hal_data->lpTextureFormats[c].ddpfPixelFormat,
		      &fmt, sizeof(DDPIXELFORMAT))) return D3D_OK;
      }
    }
    break;
  case D3DRTYPE_VERTEXBUFFER:
    if (CheckFormat == D3DFMT_VERTEXDATA)
      return D3D_OK;
    break;
  case D3DRTYPE_INDEXBUFFER:
    /* we only support 16-bit index buffers for now */
    if (CheckFormat == D3DFMT_INDEX16)
      return D3D_OK;
    break;
  default:
    break;
  }

  TRACE("=> format not available\n");
  return D3DERR_NOTAVAILABLE;
}

HRESULT WINAPI Direct3D8_CheckDeviceMultiSampleType(LPDIRECT3D8 iface,
						    UINT Adapter,
						    D3DDEVTYPE DeviceType,
						    D3DFORMAT SurfaceFormat,
						    BOOL Windowed,
						    D3DMULTISAMPLE_TYPE MultiSampleType)
{
  TRACE("(%p}->(%d,%d,%d,%d,%d)\n", iface,
	Adapter, DeviceType, SurfaceFormat, Windowed, MultiSampleType);

  if (MultiSampleType == D3DMULTISAMPLE_NONE)
    return D3D_OK;

  /* we don't support multisampling */
  return D3DERR_NOTAVAILABLE;
}

HRESULT WINAPI Direct3D8_CheckDepthStencilMatch(LPDIRECT3D8 iface,
						UINT Adapter,
						D3DDEVTYPE DeviceType,
						D3DFORMAT AdapterFormat,
						D3DFORMAT RenderTargetFormat,
						D3DFORMAT DepthStencilFormat)
{
  TRACE("(%p}->(%d,%d,%d,%d,%d)\n", iface,
	Adapter, DeviceType, AdapterFormat, RenderTargetFormat, DepthStencilFormat);

  /* pretend we can support anything */
  return D3D_OK;
}

HRESULT WINAPI Direct3D8_GetDeviceCaps(LPDIRECT3D8 iface,
				       UINT Adapter,
				       D3DDEVTYPE DeviceType,
				       D3DCAPS8* pCaps)
{
  TRACE("(%p}->(%d,%d,%p)\n", iface, Adapter, DeviceType, pCaps);
  memcpy(pCaps, d3d8caps, sizeof(D3DCAPS8));
  return D3D_OK;
}

HRESULT WINAPI Direct3D8_CreateDevice(LPDIRECT3D8 iface,
				      UINT Adapter,
				      D3DDEVTYPE DeviceType,
				      HWND hFocusWindow,
				      DWORD BehaviorFlags,
				      D3DPRESENT_PARAMETERS* pPresPar,
				      IDirect3DDevice8** ppD3DD)
{
  ICOM_THIS(IDirect3D8Impl, iface);
  IDirect3DDevice8Impl *pD3DD;
  HRESULT hr;

  TRACE("(%p}->(%d,%d,%x,%lx,%p,%p)\n", iface, Adapter, DeviceType,
	hFocusWindow, BehaviorFlags, pPresPar, ppD3DD);

  hr = Direct3DDevice8_ValidatePresent(pPresPar);
  if (FAILED(hr)) return hr;

  pD3DD = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DDevice8Impl));
  pD3DD->lpVtbl = &D3DD8_Vtbl;
  pD3DD->ref = 1;
  pD3DD->parent = This;
  IDirect3D8_AddRef(iface);
  pD3DD->caps = d3d8caps;

  pD3DD->local.lpGbl = dd_gbl;
  pD3DD->local.dwLocalFlags = DDRAWILCL_DIRECTDRAW7 | DDRAWILCL_SETCOOPCALLED;
  if (BehaviorFlags & D3DCREATE_FPU_PRESERVE)
    pD3DD->local.dwLocalFlags |= DDRAWILCL_FPUPRESERVE;
  if (BehaviorFlags & D3DCREATE_MULTITHREADED)
    pD3DD->local.dwLocalFlags |= DDRAWILCL_MULTITHREADED;
  if (!pPresPar->Windowed) {
    pD3DD->local.dwLocalFlags |= DDRAWILCL_HASEXCLUSIVEMODE | DDRAWILCL_ISFULLSCREEN;
  }
  pD3DD->local.hWnd = pPresPar->hDeviceWindow ? pPresPar->hDeviceWindow : hFocusWindow;
  pD3DD->local.hFocusWnd = hFocusWindow;
  pD3DD->behavior = BehaviorFlags;

  pD3DD->d3dp.lcl = &pD3DD->local;
  pD3DD->d3dp.gdi = dd_get_driver_info;
  pD3DD->d3dp.cbs1 = d3d_hal_cbs1;
  pD3DD->d3dp.cbs2 = d3d_hal_cbs2;

  pD3DD->d3dp.surface_heap = surface_heap;

  /* initialize FPU state */
  if (!(pD3DD->local.dwLocalFlags & DDRAWILCL_FPUPRESERVE)) {
    /* Direct3D disables exceptions and sets single-precision mode. */
    /* I'm not sure if it's 100% safe to set single-precision,
     * comment that part out if it causes problems. */
    fpu_control_t cw;
    _FPU_GETCW(cw);
    cw |= 0x3f; /* Disable exceptions */
    cw &= ~0x300; /* Set single precision */
    _FPU_SETCW(cw);
  }

  /* initialize d3dgl renderer */
  if (d3d_hal_data->hwCaps.dwFlags & D3DDD_WINE_OPENGL_DEVICE) {
    pD3DD->demolish_texture  = (LPVOID)GetProcAddress(d3dgl, "DemolishTexture");
    pD3DD->alloc_state_block = (LPVOID)GetProcAddress(d3dgl, "AllocStateBlock");
    pD3DD->free_state_block  = (LPVOID)GetProcAddress(d3dgl, "FreeStateBlock");
    pD3DD->start_dp2         = (LPVOID)GetProcAddress(d3dgl, "StartDP2");
    pD3DD->start_dp2_sd      = (LPVOID)GetProcAddress(d3dgl, "StartDP2SD");
    pD3DD->emit_dp2          = (LPVOID)GetProcAddress(d3dgl, "EmitDP2");
    pD3DD->flush_dp2         = (LPVOID)GetProcAddress(d3dgl, "FlushDP2");
    pD3DD->validate_device   = (LPVOID)GetProcAddress(d3dgl, "ValidateDevice");
  }

  if (d3d8caps->MaxVertexShaderConst)
    pD3DD->d3dp.vertex_const = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				    4*sizeof(float)*d3d8caps->MaxVertexShaderConst);
  else
    pD3DD->d3dp.vertex_const = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				    4*sizeof(float)*96);

  pD3DD->state.dirty_tex = 0xffffffff;

  *ppD3DD = (LPDIRECT3DDEVICE8)pD3DD;

  hr = Direct3DDevice8_CreateContext(*ppD3DD, pPresPar);
  if (FAILED(hr)) {
    HeapFree(GetProcessHeap(), 0, pD3DD);
    *ppD3DD = NULL;
  }

  return hr;
}

static ICOM_VTABLE(IDirect3D8) D3D8_Vtbl =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3D8_QueryInterface,
  Direct3D8_AddRef,
  Direct3D8_Release,
  (void*)0xdead0004, /* RegisterSoftwareDevice */
  Direct3D8_GetAdapterCount,
  Direct3D8_GetAdapterIdentifier,
  Direct3D8_GetAdapterModeCount,
  Direct3D8_EnumAdapterModes,
  Direct3D8_GetAdapterDisplayMode,
  Direct3D8_CheckDeviceType,
  Direct3D8_CheckDeviceFormat,
  Direct3D8_CheckDeviceMultiSampleType,
  Direct3D8_CheckDepthStencilMatch,
  Direct3D8_GetDeviceCaps,
  (void*)0xdead000f, /* GetAdapterMonitor */
  Direct3D8_CreateDevice
};

/******************************************************************
 *		Direct3DCreate8 (D3D8.@)
 */
LPDIRECT3D8 WINAPI Direct3DCreate8(UINT SDKVersion)
{
  IDirect3D8Impl *pD3D;
  TRACE("SDKVersion=%d\n", SDKVersion);
  pD3D = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3D8Impl));
  pD3D->lpVtbl = &D3D8_Vtbl;
  pD3D->ref = 1;
  return (LPDIRECT3D8)pD3D;
}

/******************************************************************
 *		DebugSetMute (D3D8.@)
 */
DWORD CDECL DebugSetMute(DWORD unk)
{
  /* this function is undocumented */
  TRACE("(%ld): undocumented\n", unk);
  return 0;
}

static D3DCAPS8* D3DHAL_ConvertCaps(void)
{
  D3DCAPS8 *caps;
  caps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(D3DCAPS8));
  if (!caps) return NULL;
  caps->DeviceType = D3DDEVTYPE_HAL;
  caps->Caps = dd_gbl->ddCaps.dwCaps;
  caps->Caps2 = dd_gbl->ddCaps.dwCaps2;
  caps->Caps3 = 0; /* FIXME */
  caps->DevCaps = d3d_hal_data->hwCaps.dwDevCaps;
  caps->PrimitiveMiscCaps  = d3d_hal_data->hwCaps.dpcTriCaps.dwMiscCaps;
  caps->RasterCaps         = d3d_hal_data->hwCaps.dpcTriCaps.dwRasterCaps;
  caps->ZCmpCaps           = d3d_hal_data->hwCaps.dpcTriCaps.dwZCmpCaps;
  caps->SrcBlendCaps       = d3d_hal_data->hwCaps.dpcTriCaps.dwSrcBlendCaps;
  caps->DestBlendCaps      = d3d_hal_data->hwCaps.dpcTriCaps.dwDestBlendCaps;
  caps->AlphaCmpCaps       = d3d_hal_data->hwCaps.dpcTriCaps.dwAlphaCmpCaps;
  caps->ShadeCaps          = d3d_hal_data->hwCaps.dpcTriCaps.dwShadeCaps;
  caps->TextureCaps        = d3d_hal_data->hwCaps.dpcTriCaps.dwTextureCaps;
  caps->TextureFilterCaps  = d3d_hal_data->hwCaps.dpcTriCaps.dwTextureFilterCaps;
  caps->TextureAddressCaps = d3d_hal_data->hwCaps.dpcTriCaps.dwTextureAddressCaps;
  /* FIXME: CubeTextureFilterCaps and VolumeTextureFilterCaps */
  /* FIXME: LineCaps */
  caps->MaxTextureWidth       = d3d_hal_extcaps->dwMaxTextureWidth;
  caps->MaxTextureHeight      = d3d_hal_extcaps->dwMaxTextureHeight;
  /* FIXME: MaxVolumeExtent */
  caps->MaxTextureRepeat      = d3d_hal_extcaps->dwMaxTextureRepeat;
  caps->MaxTextureAspectRatio = d3d_hal_extcaps->dwMaxTextureAspectRatio;
  caps->MaxAnisotropy         = d3d_hal_extcaps->dwMaxAnisotropy;
  caps->MaxVertexW            = d3d_hal_extcaps->dvMaxVertexW;
  caps->GuardBandLeft   = d3d_hal_extcaps->dvGuardBandLeft;
  caps->GuardBandTop    = d3d_hal_extcaps->dvGuardBandTop;
  caps->GuardBandRight  = d3d_hal_extcaps->dvGuardBandRight;
  caps->GuardBandBottom = d3d_hal_extcaps->dvGuardBandBottom;
  caps->ExtentsAdjust = d3d_hal_extcaps->dvExtentsAdjust;
  caps->StencilCaps   = d3d_hal_extcaps->dwStencilCaps;
  caps->FVFCaps       = d3d_hal_extcaps->dwFVFCaps;
  caps->TextureOpCaps = d3d_hal_extcaps->dwTextureOpCaps;
  caps->MaxTextureBlendStages   = d3d_hal_extcaps->wMaxTextureBlendStages;
  caps->MaxSimultaneousTextures = d3d_hal_extcaps->wMaxSimultaneousTextures;
  caps->VertexProcessingCaps = d3d_hal_extcaps->dwVertexProcessingCaps;
  caps->MaxActiveLights   = d3d_hal_extcaps->dwMaxActiveLights;
  caps->MaxUserClipPlanes = d3d_hal_extcaps->wMaxUserClipPlanes;
  caps->MaxVertexBlendMatrices = d3d_hal_extcaps->wMaxVertexBlendMatrices;
  caps->MaxVertexBlendMatrixIndex = 0; /* FIXME */
  caps->MaxPointSize = 1.0f;
  caps->MaxPrimitiveCount = 0xFFFF;
  caps->MaxVertexIndex = 0xFFFF;

  /* FIXME: additional D3D8 caps */

  return caps;
}

void init_std_format(void)
{
  orig_width = GetSystemMetrics(SM_CXSCREEN);
  orig_height = GetSystemMetrics(SM_CYSCREEN);
  switch (GetSystemMetrics(SM_WINE_BPP)) {
  case 8:
    std_format = D3DFMT_P8;
    break;
  case 15:
    std_format = D3DFMT_X1R5G5B5;
    break;
  case 16:
    std_format = D3DFMT_R5G6B5;
    break;
  case 24:
    std_format = D3DFMT_R8G8B8;
    break;
  case 32:
    std_format = D3DFMT_X8R8G8B8;
    break;
  default:
    std_format = D3DFMT_UNKNOWN;
    break;
  }
  TRACE("desktop format: %d\n", std_format);
}

static void HAL_initialize(void)
{
  dx8_hal = FALSE;
  __wine_get_hal(&dd_gbl, &dd_get_driver_info);
  if (dd_gbl) {
    dd_cbs = dd_gbl->lpDDCBtmp;
    d3d_hal_data = (LPVOID)dd_gbl->lpD3DGlobalDriverData;
    d3d_hal_extcaps = (LPVOID)dd_gbl->lpD3DExtendedCaps;
    d3d_hal_cbs1 = (LPVOID)dd_gbl->lpD3DHALCallbacks;
    d3d_hal_cbs2 = (LPVOID)dd_gbl->lpD3DHALCallbacks2;
    if (d3d_hal_data->hwCaps.dwFlags & D3DDD_WINE_OPENGL_DEVICE) {
      d3dgl = GetModuleHandleA("d3dgl.dll");
      if (!d3d8caps) {
        D3DGetCapsFunc func;
        func = (LPVOID)GetProcAddress(d3dgl, "GetCaps");
        if (func) d3d8caps = func(dd_gbl, 8);
        if (d3d8caps) dx8_hal = TRUE;
      }
    }
    if (!d3d8caps)
      d3d8caps = D3DHAL_ConvertCaps();
  }
  else {
    dd_cbs = NULL;
    d3d_hal_data = NULL;
    d3d_hal_extcaps = NULL;
    d3d_hal_cbs1 = NULL;
    d3d_hal_cbs2 = NULL;
    memset(&d3d8caps, 0, sizeof(D3DCAPS8));
    d3dgl = 0;
  }
  TRACE("%s DirectDraw HAL\n", dd_gbl ? "enabling" : "disabling");
}

BOOL WINAPI D3D8_DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpv)
{
  switch (fdwReason) {
  case DLL_PROCESS_ATTACH:
    buffer_heap = HeapCreate(HEAP_GROWABLE, 0, 0);
    surface_heap = HeapCreate(HEAP_GROWABLE, 0, 0);
    init_std_format();
    HAL_initialize();
    break;
  case DLL_PROCESS_DETACH:
    HeapDestroy(surface_heap);
    HeapDestroy(buffer_heap);
    surface_heap = 0;
    break;
  }
  return TRUE;
}
