/* Direct3D Device
   (c) 1998 Lionel ULMER
   Copyright 2000-2001 TransGaming Technologies Inc.

   This files contains all the common stuff for D3D devices.
 */

#include "config.h"

#include <string.h>

#include "windef.h"
#include "winerror.h"
#include "objbase.h"
#include "ddraw.h"
#include "d3d.h"
#include "wine/debug.h"
#include "ddrawi.h"
#include "d3d_private.h"
#include "d3ddevice/main.h"
#include "d3ddevice/maths.h"
#include "d3ddevice/viewport.h"
#include "d3ddevice/material.h"
#include "ddraw/main.h"
#include "d3ddevice/exebuf.h"


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 _dump_colorvalue(s,v) \
    TRACE(" " s " : %f %f %f %f\n", (v).u1.r, (v).u2.g, (v).u3.b, (v).u4.a)

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

static ICOM_VTABLE(IDirect3DDevice7) Main_IDirect3DDevice7_VTable;
static ICOM_VTABLE(IDirect3DDevice3) Main_IDirect3DDevice3_VTable;
static ICOM_VTABLE(IDirect3DDevice2) Main_IDirect3DDevice2_VTable;

static IDirectDrawSurfaceImpl* get_zbuffer(IDirectDrawSurfaceImpl *surf)
{
    static DDSCAPS2 z_caps = { DDSCAPS_ZBUFFER };
    LPDIRECTDRAWSURFACE7 zbufi = NULL;
    IDirectDrawSurfaceImpl *zbuf;
    IDirectDrawSurface7_GetAttachedSurface(ICOM_INTERFACE(surf, IDirectDrawSurface7),
					   &z_caps, &zbufi);
    if (zbufi) {
	zbuf = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7, zbufi);
	IDirectDrawSurface7_Release(zbufi);
	return zbuf;
    }
    else return NULL;
}

/* Non-interface functions */
HRESULT
Main_Direct3DDevice_Construct(IDirect3DDeviceImpl* This,
			      IDirectDrawImpl* pDD,
			      IDirectDrawSurfaceImpl* pDDS)
{
    This->ref = 1;
    This->ddraw_owner = pDD;
    IDirectDrawSurface7_AddRef(ICOM_INTERFACE(pDDS, IDirectDrawSurface7));
    This->surface = pDDS;
    This->zbuffer = get_zbuffer(pDDS);
    This->final_release = Main_Direct3DDevice_final_release;
    This->setup_render_target = Main_Direct3DDevice_setup_render_target;
    This->late_allocate_surface = Main_Direct3DDevice_late_allocate_surface;
    This->demolish_texture = Main_Direct3DDevice_demolish_texture;
    This->current = &This->state;
    This->state.dirty_tex = 0xffffffff;

    ICOM_INIT_INTERFACE(This, IDirect3DDevice7,
			Main_IDirect3DDevice7_VTable);
    ICOM_INIT_INTERFACE(This, IDirect3DDevice3,
			Main_IDirect3DDevice3_VTable);
    ICOM_INIT_INTERFACE(This, IDirect3DDevice2,
			Main_IDirect3DDevice2_VTable);
    ICOM_INIT_INTERFACE(This, IDirect3DDevice,
			Main_IDirect3DDevice1_VTable);

    Main_DirectDraw_Add3DDevice(This->ddraw_owner, This);

    return D3D_OK;
}

void Main_Direct3DDevice_final_release(IDirect3DDeviceImpl* This)
{
    unsigned u;
    Main_DirectDraw_Remove3DDevice(This->ddraw_owner, This);
    for (u=0; u<8; u++) if (This->state.tex[u]) {
	LPDIRECTDRAWSURFACE7 lpTex = This->state.tex[u];
	IDirectDrawSurface7_Release(lpTex);
    }
    IDirectDrawSurface7_Release(ICOM_INTERFACE(This->surface, IDirectDrawSurface7));
}

static void Main_Direct3DDevice_Destroy(IDirect3DDeviceImpl* This)
{
    This->final_release(This);
    if (This->private != This+1) HeapFree(GetProcessHeap(), 0, This->private);
    HeapFree(GetProcessHeap(), 0, This);
}

void Main_Direct3DDevice_ForceDestroy(IDirect3DDeviceImpl* This)
{
    WARN("destroying 3D device %p with refcnt %lu\n", This, This->ref);
    Main_Direct3DDevice_Destroy(This);
}

void Main_Direct3DDevice_post_init(IDirect3DDeviceImpl* This)
{
    LPDIRECT3DDEVICE7 iface = ICOM_INTERFACE(This, IDirect3DDevice7);
    LPDDRAWI_DIRECTDRAW_GBL dd_gbl = This->ddraw_owner->local.lpGbl;
    LPD3DHAL_D3DEXTENDEDCAPS extcaps = (LPD3DHAL_D3DEXTENDEDCAPS)dd_gbl->lpD3DExtendedCaps;
    unsigned u;
    D3DMATRIX identity_mat;
    D3DCLIPSTATUS clip_status;
    D3DVIEWPORT7 vp;

    This->setup_render_target(This);

    TRACE("initializing default render states\n");

    /* Set up an identity matrix for later use */
    identity_mat._11 = identity_mat._22 = identity_mat._33 = identity_mat._44 = 1.0f;
    identity_mat._12 = identity_mat._13 = identity_mat._14 = identity_mat._41 = 0.0f;
    identity_mat._21 = identity_mat._23 = identity_mat._24 = identity_mat._42 = 0.0f;
    identity_mat._31 = identity_mat._32 = identity_mat._34 = identity_mat._43 = 0.0f;

    vp.dwX = 0;
    vp.dwY = 0;
    vp.dwWidth = This->surface->surface_desc.dwWidth;
    vp.dwHeight = This->surface->surface_desc.dwHeight;
    vp.dvMinZ = 0;
    vp.dvMaxZ = 1;
    IDirect3DDevice7_SetViewport(iface, &vp);

    clip_status.dwFlags = D3DCLIPSTATUS_STATUS | D3DCLIPSTATUS_EXTENTS2;
    clip_status.dwStatus = D3DSTATUS_DEFAULT;
    /* We can't set these to infinity, as one program checks to ensure they
     * are within +- 32767. */
    clip_status.minx = 32767;
    clip_status.maxx = -32767;
    clip_status.miny = 32767;
    clip_status.maxy = -32767;
    clip_status.minz = 0;
    clip_status.maxz = 1;
    IDirect3DDevice7_SetClipStatus(iface, &clip_status);

    IDirect3DDevice7_SetTransform(iface, D3DTRANSFORMSTATE_WORLD,
				  &identity_mat);
    IDirect3DDevice7_SetTransform(iface, D3DTRANSFORMSTATE_VIEW,
				  &identity_mat);
    IDirect3DDevice7_SetTransform(iface, D3DTRANSFORMSTATE_PROJECTION,
				  &identity_mat);

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

    /* TRUE for IDirect3D3 and IDirect3D7, FALSE for earlier interfaces */
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_TEXTUREPERSPECTIVE, TRUE);

    /* D3DRENDERSTATE_ZENABLE is handled in Main_Direct3DDevice_setup_render_target */

    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_FILLMODE, D3DFILL_SOLID);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_SHADEMODE, D3DSHADE_GOURAUD);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_ZWRITEENABLE, TRUE);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_LASTPIXEL, TRUE);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_DESTBLEND, D3DBLEND_ZERO);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_CULLMODE, D3DCULL_CCW);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_ZFUNC, D3DCMP_LESSEQUAL);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_ALPHAFUNC, D3DCMP_ALWAYS);

    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_STENCILFAIL, D3DSTENCILOP_KEEP);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_STENCILZFAIL, D3DSTENCILOP_KEEP);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_STENCILPASS, D3DSTENCILOP_KEEP);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_STENCILFUNC, D3DCMP_ALWAYS);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_STENCILMASK, 0xffffffff);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_STENCILWRITEMASK, 0xffffffff);

    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_CLIPPING, TRUE);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_LIGHTING, TRUE);

    if (!(This->ddraw_owner->local.dwLocalFlags & DDRAWILCL_DIRECTDRAW7))
	IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_EXTENTS, TRUE);

    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_COLORVERTEX, TRUE);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_LOCALVIEWER, TRUE);

    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_SPECULARMATERIALSOURCE, D3DMCS_COLOR2);
    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_AMBIENTMATERIALSOURCE, D3DMCS_COLOR2);

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

    for (u=0; u<extcaps->wMaxTextureBlendStages; u++) {
	IDirect3DDevice7_SetTransform(iface, D3DTRANSFORMSTATE_TEXTURE0+u, &identity_mat);

	if( u>0 ) {
	  IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_COLOROP, D3DTOP_DISABLE);
	  IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
	}
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_COLORARG2, D3DTA_CURRENT);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_TEXCOORDINDEX, u);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_ADDRESS, D3DTADDRESS_WRAP);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_MAGFILTER, D3DTFG_POINT);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_MINFILTER, D3DTFN_POINT);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_MIPFILTER, D3DTFP_NONE);
	IDirect3DDevice7_SetTextureStageState(iface, u, D3DTSS_MAXANISOTROPY, 1);
    }
}

void Main_Direct3DDevice_setup_render_target(IDirect3DDeviceImpl *This)
{
    LPDIRECT3DDEVICE7 iface = ICOM_INTERFACE(This, IDirect3DDevice7);
    if (This->zbuffer) {
	if (This->d3dp.renderstate[D3DRENDERSTATE_ZENABLE] == D3DZB_FALSE)
	    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_ZENABLE, D3DZB_TRUE);
    }
    else {
	if (This->d3dp.renderstate[D3DRENDERSTATE_ZENABLE] != D3DZB_FALSE)
	    IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_ZENABLE, D3DZB_FALSE);
    }
}

HRESULT Main_Direct3DDevice_late_allocate_surface(IDirect3DDeviceImpl* This,
						  IDirectDrawSurfaceImpl* pDDS)
{
    return pDDS->late_allocate(pDDS);
}

void WINAPI Main_Direct3DDevice_demolish_texture(LPVOID ctx, DWORD dwTexture)
{
}

void Main_Direct3DDevice_req_load_texture(IDirectDrawSurfaceImpl* texture,
					  IDirectDrawSurfaceImpl* source)
{
    IDirectDrawImpl *ddraw = texture->ddraw_owner;
    IDirect3DDeviceImpl *device;

    TRACE("(%p,%p)\n", texture, source);
    for (device = ddraw->devices; device; device = device->next_ddraw) {
	LPDIRECT3DDEVICE7 dev = ICOM_INTERFACE(device, IDirect3DDevice7);
	TRACE("=>(%p)\n", device);
	IDirect3DDevice7_Load(dev, ICOM_INTERFACE(texture, IDirectDrawSurface7),
			      NULL, ICOM_INTERFACE(source, IDirectDrawSurface7),
			      NULL, 0);
    }
}

void Main_Direct3DDevice_req_invalidate_texture(IDirectDrawSurfaceImpl* texture,
						LPCRECT pRect)
{
    IDirectDrawImpl *ddraw = texture->ddraw_owner;
    IDirect3DDeviceImpl *device;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXBLT, 0, {1} };
    D3DHAL_DP2TEXBLT data;

    TRACE("(%p,%p)\n", texture, pRect);
    data.dwDDDestSurface = texture ? (DWORD)&texture->t : 0;
    data.dwDDSrcSurface = 0;
    data.pDest.x = 0;
    data.pDest.y = 0;
    if (pRect) {
	data.rSrc.left   = pRect->left;
	data.rSrc.top    = pRect->top;
	data.rSrc.right  = pRect->right;
	data.rSrc.bottom = pRect->bottom;
    } else {
	data.rSrc.left   = 0;
	data.rSrc.top    = 0;
	data.rSrc.right  = texture->surface_desc.dwWidth;
	data.rSrc.bottom = texture->surface_desc.dwHeight;
    }
    data.dwFlags = 0;
    for (device = ddraw->devices; device; device = device->next_ddraw) {
	TRACE("=>(%p)\n", device);
	device->emit_dp2(device->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
    }
}

void Main_Direct3DDevice_req_demolish_texture(IDirectDrawSurfaceImpl* texture)
{
    IDirectDrawImpl *ddraw = texture->ddraw_owner;
    DWORD dwTexture = (DWORD)texture;
    IDirect3DDeviceImpl *device;

    TRACE("(%p)\n", texture);
    for (device = ddraw->devices; device; device = device->next_ddraw) {
	TRACE("=>(%p)\n", device);
	device->demolish_texture(device->ip_ctx, dwTexture);
    }
}

/* Interface functions */
ULONG WINAPI Main_Direct3DDevice_Release(LPDIRECT3DDEVICE7 iface)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);

    if (--This->ref == 0)
    {
	Main_Direct3DDevice_Destroy(This);
	return 0;
    }

    return This->ref;
}

ULONG WINAPI Main_Direct3DDevice_AddRef(LPDIRECT3DDEVICE7 iface)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);

    return ++This->ref;
}

HRESULT WINAPI
Main_Direct3DDevice_QueryInterface(LPDIRECT3DDEVICE7 iface, REFIID riid,
				   LPVOID* ppObj)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppObj);

    if (IsEqualGUID(&IID_IUnknown, riid)
	|| IsEqualGUID(&IID_IDirect3DDevice7, riid))
    {
	This->ref++;
	*ppObj = ICOM_INTERFACE(This, IDirect3DDevice7);
	return S_OK;
    }
    else if (IsEqualGUID(&IID_IDirect3DDevice3, riid))
    {
	This->ref++;
	*ppObj = ICOM_INTERFACE(This, IDirect3DDevice3);
	return S_OK;
    }
    else if (IsEqualGUID(&IID_IDirect3DDevice2, riid))
    {
	This->ref++;
	*ppObj = ICOM_INTERFACE(This, IDirect3DDevice2);
	return S_OK;
    }
    else if (IsEqualGUID(&IID_IDirect3DDevice, riid))
    {
	This->ref++;
	*ppObj = ICOM_INTERFACE(This, IDirect3DDevice);
	return S_OK;
    }
    else {
	ERR("unknown 3D interface %s\n", debugstr_guid(riid));
	return E_NOINTERFACE;
    }
}

HRESULT WINAPI
Main_Direct3DDevice_GetCaps(LPDIRECT3DDEVICE7 iface, LPD3DDEVICEDESC7 lpCaps)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    LPDDRAWI_DIRECTDRAW_GBL dd_gbl = This->ddraw_owner->local.lpGbl;
    LPD3DHAL_GLOBALDRIVERDATA data = (LPD3DHAL_GLOBALDRIVERDATA)dd_gbl->lpD3DGlobalDriverData;
    LPD3DHAL_D3DEXTENDEDCAPS extcaps = (LPD3DHAL_D3DEXTENDEDCAPS)dd_gbl->lpD3DExtendedCaps;

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

    Direct3D7_MakeDeviceDesc(lpCaps, &data->hwCaps, extcaps, (LPGUID)&IID_IDirect3DTnLHalDevice);

    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_EnumTextureFormats(LPDIRECT3DDEVICE7 iface,
				       LPD3DENUMPIXELFORMATSCALLBACK lpCB,
				       LPVOID lpContext)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    LPDDRAWI_DIRECTDRAW_GBL dd_gbl = This->ddraw_owner->local.lpGbl;
    LPD3DHAL_GLOBALDRIVERDATA data = (LPD3DHAL_GLOBALDRIVERDATA)dd_gbl->lpD3DGlobalDriverData;
    unsigned i;

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

    for (i=0; i<data->dwNumTextureFormats; i++) {
	if (!lpCB(&data->lpTextureFormats[i].ddpfPixelFormat, lpContext))
	    return D3D_OK;
    }

    return D3D_OK;
}

HRESULT WINAPI Main_Direct3DDevice_BeginScene(LPDIRECT3DDEVICE7 iface)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    HRESULT hr;
    TRACE("(%p): surface=%p\n", This, This->surface);
    if (This->d3dp.in_scene) return D3DERR_SCENE_IN_SCENE;

    SAVE_AND_CLEAN_FPU_STATE();
    FPS_BEGIN_SCENE(); 

    /* initialize rendering, possibly locking rendering resources */
    This->d3dp.in_scene = TRUE;
    hr = This->begin_scene(This);
    if (FAILED(hr)) {
	This->d3dp.in_scene = FALSE;
	return hr;
    }

#ifdef WIREFRAME
    /* Wireframe HACK - toggle wireframe display state when Control is pressed */
    gWireframe = GetKeyState(VK_CONTROL);
    if (gWireframe)
    {
        IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_FILLMODE, D3DFILL_WIREFRAME);
        /* For apps that rerender every pixel, every frame, we have to force a clear  -
           DISABLED for now!
        IDirect3DDevice7_Clear( iface, 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0L );
        */
    }
    else
        IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_FILLMODE, D3DFILL_SOLID);
#endif

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

    return hr;
}

HRESULT WINAPI Main_Direct3DDevice_EndScene(LPDIRECT3DDEVICE7 iface)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    HRESULT hr;
    TRACE("(%p): surface=%p\n", This, This->surface);
    if (!This->d3dp.in_scene) return D3DERR_SCENE_NOT_IN_SCENE;

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

    /* shutdown rendering, releasing any locked rendering resources */
    hr = This->end_scene(This);
    This->d3dp.in_scene = FALSE;

    FPS_END_SCENE();
    RESTORE_FPU_STATE();

    return hr;
}

HRESULT WINAPI
Main_Direct3DDevice_GetDirect3D(LPDIRECT3DDEVICE7 iface, LPDIRECT3D7*lplpDirect3D)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%p)\n", This, lplpDirect3D);
    *lplpDirect3D = ICOM_INTERFACE(This->ddraw_owner, IDirect3D7);
    IDirect3D7_AddRef(*lplpDirect3D);
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_SetRenderTarget(LPDIRECT3DDEVICE7 iface, LPDIRECTDRAWSURFACE7 lpDDS, DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    IDirectDrawSurfaceImpl* surf = ICOM_OBJECT(IDirectDrawSurfaceImpl,
					       IDirectDrawSurface7,
					       lpDDS);
    IDirectDrawSurfaceImpl* zsurf = get_zbuffer(surf);
    HRESULT hr;
    TRACE("(%p)->(%p,%08lx): zbuf=%p\n", This, surf, dwFlags, zsurf);
    hr = This->set_render_target(This, surf, zsurf, dwFlags);
    if (SUCCEEDED(hr)) {
        IDirectDrawSurface7_AddRef(ICOM_INTERFACE(surf, IDirectDrawSurface7));
        if (This->surface) IDirectDrawSurface7_Release(ICOM_INTERFACE(This->surface, IDirectDrawSurface7));
        This->surface = surf;
        This->zbuffer = zsurf;
        This->setup_render_target(This);
    }
    return hr;
}

HRESULT WINAPI
Main_Direct3DDevice_GetRenderTarget(LPDIRECT3DDEVICE7 iface,
				    LPDIRECTDRAWSURFACE7* lplpDDS)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    *lplpDDS = ICOM_INTERFACE(This->surface, IDirectDrawSurface7);
    TRACE("(%p)->(%p) => %p\n", This, lplpDDS, *lplpDDS);
    if (*lplpDDS) IDirectDrawSurface7_AddRef(*lplpDDS);
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_Clear(LPDIRECT3DDEVICE7 iface, DWORD dwCount,
			  LPD3DRECT lpRects, DWORD dwFlags,
			  D3DCOLOR dwColor, D3DVALUE dvZ,
			  DWORD dwStencil)
{
    ICOM_THIS(IDirect3DDeviceImpl, 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) IDirect3DDevice7_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),
			lpRects, dwCount*sizeof(RECT));
    /* FIXME: the GL device might only process DP2 commands after BeginScene
     * has locked rendering resources, so if issued outside BeginScene,
     * the clear may be delayed until BeginScene is called. Is this okay,
     * or should we use a separate route into the driver for this purpose?
     */
    if (!was_in_scene) IDirect3DDevice7_EndScene(iface);
    return hr;
}

HRESULT WINAPI
Main_Direct3DDevice_SetTransform(LPDIRECT3DDEVICE7 iface,
				 D3DTRANSFORMSTATETYPE dtstType,
				 LPD3DMATRIX lpD3DMatrix)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETTRANSFORM, 0, {1} };
    D3DHAL_DP2SETTRANSFORM xfrm;

    TRACE("(%p)->(%d,%p)\n", This, dtstType, lpD3DMatrix);
    if (!dtstType) return DDERR_INVALIDPARAMS;
    if (dtstType >= D3DTRANSFORMSTATE_TEXTURE0) {
	unsigned i = dtstType - D3DTRANSFORMSTATE_TEXTURE0;
	if (i >= 8) return DDERR_INVALIDPARAMS;
	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
Main_Direct3DDevice_GetTransform(LPDIRECT3DDEVICE7 iface,
				 D3DTRANSFORMSTATETYPE dtstType,
				 LPD3DMATRIX lpD3DMatrix)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%d,%p)\n", This, dtstType, lpD3DMatrix);
    if (!dtstType) return DDERR_INVALIDPARAMS;
    if (dtstType >= D3DTRANSFORMSTATE_TEXTURE0) {
	unsigned i = dtstType - D3DTRANSFORMSTATE_TEXTURE0;
	if (i >= 8) return DDERR_INVALIDPARAMS;
	*lpD3DMatrix = This->d3dp.txfrm[i];
    }
    else if (dtstType < 8) {
	*lpD3DMatrix = This->d3dp.xfrm[dtstType];
    }
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_MultiplyTransform(LPDIRECT3DDEVICE7 iface,
				      D3DTRANSFORMSTATETYPE dtstType,
				      LPD3DMATRIX lpD3DMatrix)
{
    D3DMATRIX curMatrix;
    D3DMATRIX resultMatrix;
    HRESULT  result;
    ICOM_THIS(IDirect3DDeviceImpl, iface);

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

    if (!lpD3DMatrix)
        return D3D_OK;

    result = Main_Direct3DDevice_GetTransform(iface, dtstType, &curMatrix);

    if (result != D3D_OK)
        return result;

    MatrixMultiply(&resultMatrix, lpD3DMatrix, &curMatrix);

    result = Main_Direct3DDevice_SetTransform(iface, dtstType, &resultMatrix);
    return result;
}

HRESULT WINAPI
Main_Direct3DDevice_SetViewport(LPDIRECT3DDEVICE7 iface,
				LPD3DVIEWPORT7 lpData)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd = { 0, 0, {1} };
    D3DHAL_DP2VIEWPORTINFO view;
    D3DHAL_DP2ZRANGE zrange;
    HRESULT hr;

    TRACE("(%p)->(%p)\n", This, lpData);
    This->d3dp.viewport = *lpData;
    view.dwX = lpData->dwX;
    view.dwY = lpData->dwY;
    view.dwWidth  = lpData->dwWidth;
    view.dwHeight = lpData->dwHeight;
    zrange.dvMinZ = lpData->dvMinZ;
    zrange.dvMaxZ = lpData->dvMaxZ;
    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
Main_Direct3DDevice_GetViewport(LPDIRECT3DDEVICE7 iface,
				LPD3DVIEWPORT7 lpData)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpData);
    *lpData = This->d3dp.viewport;
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_SetMaterial(LPDIRECT3DDEVICE7 iface,
				LPD3DMATERIAL7 lpMat)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETMATERIAL, 0, {1} };

    TRACE("(%p)->(%p)\n", This, lpMat);
    _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);
    return This->emit_dp2(This->ip_ctx, &cmd, lpMat, sizeof(This->d3dp.material), NULL, 0);
}

HRESULT WINAPI
Main_Direct3DDevice_GetMaterial(LPDIRECT3DDEVICE7 iface,
				LPD3DMATERIAL7 lpMat)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpMat);
    *lpMat = This->d3dp.material;
    return D3D_OK;
}

static HRESULT CheckExpandLights(IDirect3DDeviceImpl *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], &Main_Direct3DDevice_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
Main_Direct3DDevice_SetLight(LPDIRECT3DDEVICE7 iface,
			     DWORD dwIndex,
			     LPD3DLIGHT7 lpLight)
{
    ICOM_THIS(IDirect3DDeviceImpl, 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 = *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
Main_Direct3DDevice_GetLight(LPDIRECT3DDEVICE7 iface,
			     DWORD dwIndex,
			     LPD3DLIGHT7 lpLight)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpLight);
    if (dwIndex < This->d3dp.numlights)
	*lpLight = This->d3dp.light[dwIndex].l;
    else
	*lpLight = Main_Direct3DDevice_default_light.l;
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_SetRenderState(LPDIRECT3DDEVICE7 iface,
				   D3DRENDERSTATETYPE d3dType,
				   DWORD dwState)
{
    ICOM_THIS(IDirect3DDeviceImpl, 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);
	/* certain old stuff should map to SetTextureStageState
	 * for compatibility, though */
	return DDERR_INVALIDPARAMS;
    }
    /* 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
Main_Direct3DDevice_GetRenderState(LPDIRECT3DDEVICE7 iface,
				   D3DRENDERSTATETYPE d3dType,
				   LPDWORD lpdwState)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    if (d3dType >= 256) {
	WARN("out-of-range render state (%d)\n", d3dType);
	/* certain old stuff should map to GetTextureStageState
	 * for compatibility, though */
	return DDERR_INVALIDPARAMS;
    }
    *lpdwState = This->d3dp.renderstate[d3dType];
    TRACE("(%p)->(%d,%p) => %08lx\n", This, d3dType, lpdwState, *lpdwState);
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_BeginStateBlock(LPDIRECT3DDEVICE7 iface)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DStateBlock* block;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
    D3DHAL_DP2STATESET data;
    HRESULT hr;

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

    block = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(D3DStateBlock));

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

HRESULT WINAPI
Main_Direct3DDevice_EndStateBlock(LPDIRECT3DDEVICE7 iface,
				  LPDWORD lpdwBlock)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DStateBlock* block;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
    D3DHAL_DP2STATESET data;

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

    block = This->current;
    *lpdwBlock = (DWORD)block;
    This->current = &This->state;
    TRACE(" => %ld\n", *lpdwBlock);

    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
Main_Direct3DDevice_PreLoad(LPDIRECT3DDEVICE7 iface,
			    LPDIRECTDRAWSURFACE7 lpTexture)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    IDirectDrawSurfaceImpl* tex = ICOM_OBJECT(IDirectDrawSurfaceImpl,
					      IDirectDrawSurface7,
					      lpTexture);
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXBLT, 0, {1} };
    D3DHAL_DP2TEXBLT data;

    TRACE("(%p)->(%p)\n", This, lpTexture);
    data.dwDDDestSurface = 0;
    data.dwDDSrcSurface = tex ? (DWORD)&tex->t : 0;
    data.pDest.x = 0;
    data.pDest.y = 0;
    data.rSrc.left   = 0;
    data.rSrc.top    = 0;
    data.rSrc.right  = tex->surface_desc.dwWidth;
    data.rSrc.bottom = tex->surface_desc.dwHeight;
    data.dwFlags = 0;
    return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

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

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

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

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

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

    case D3DPT_TRIANGLEFAN:
	cmd->bCommand = D3DDP2OP_TRIANGLEFAN;
	cmd->u.wPrimitiveCount = 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/2;
	break; /* emit STARTVERTEX and indices */

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

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

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

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

    default:
	return 0;
    }
    return TRUE;
}

void WINAPI
Main_Direct3DDevice_updateclipstatus(IDirect3DDeviceImpl* This,
				     DWORD fvf, DWORD stride,
				     LPVOID vertices, DWORD offset,
				     DWORD num_vertices, LPWORD indices)
{
    D3DVALUE min_pre[4], max_pre[4];

    if (stride == 0)
	stride = D3D_FVF2Size(fvf);

    min_pre[0] = min_pre[1] = min_pre[2] = FLT_MAX;
    max_pre[0] = max_pre[1] = max_pre[2] = FLT_MIN;
    min_pre[3] = max_pre[3] = 1;

    vertices = (LPBYTE)vertices + offset * stride;

    if (indices)
    {
	while (num_vertices-- > 0)
	{
	    D3DVALUE* v = (D3DVALUE*)((LPBYTE)vertices + stride * *indices++);

	    if (min_pre[0] > v[0]) min_pre[0] = v[0];
	    if (max_pre[0] < v[0]) max_pre[0] = v[0];
	    if (min_pre[1] > v[1]) min_pre[1] = v[1];
	    if (max_pre[1] < v[1]) max_pre[1] = v[1];
	    if (min_pre[2] > v[2]) min_pre[2] = v[2];
	    if (max_pre[2] < v[2]) max_pre[2] = v[2];
	}
    }
    else
    {
	D3DVALUE* v = vertices;

	while (num_vertices-- > 0)
	{
	    if (min_pre[0] > v[0]) min_pre[0] = v[0];
	    if (max_pre[0] < v[0]) max_pre[0] = v[0];
	    if (min_pre[1] > v[1]) min_pre[1] = v[1];
	    if (max_pre[1] < v[1]) max_pre[1] = v[1];
	    if (min_pre[2] > v[2]) min_pre[2] = v[2];
	    if (max_pre[2] < v[2]) max_pre[2] = v[2];

	    v = (D3DVALUE*)((LPBYTE)v + stride);
	}
    }

    if ((fvf & D3DFVF_POSITION_MASK) != D3DFVF_XYZRHW)
    {
	int i, j;
	D3DVALUE min_post[4], max_post[4];
	/* See Graphics Gems: Transforming Axis-Aligned Bounding Boxes. */

	D3DMATRIX m, tmp;
	D3DVALUE (*mp)[4]; /* m[column][row] */

	MatrixMultiply(&tmp, &This->d3dp.xfrm[D3DTRANSFORMSTATE_VIEW],
		       &This->d3dp.xfrm[D3DTRANSFORMSTATE_PROJECTION]);
	MatrixMultiply(&m, &This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD], &tmp);

	mp = (LPVOID)&m;

	for (i = 0; i < 4; i++)
	{
	    min_post[i] = 0;
	    max_post[i] = 0;

	    for (j = 0; j < 4; j++)
	    {
		D3DVALUE a = mp[j][i] * min_pre[j];
		D3DVALUE b = mp[j][i] * max_pre[j];
		min_post[i] += min(a, b);
		max_post[i] += max(a, b);
	    }
	}

	/* perspective divide */
	min_post[0] /= min_post[3];
	min_post[1] /= min_post[3];
	min_post[2] /= min_post[3];
	max_post[0] /= max_post[3];
	max_post[1] /= max_post[3];
	max_post[2] /= max_post[3];

	/* viewport transform */
	min_post[0] = (min_post[0] + 1) * This->d3dp.viewport.dwWidth / 2
	    + This->d3dp.viewport.dwX;
	max_post[0] = (max_post[0] + 1) * This->d3dp.viewport.dwWidth / 2
	    + This->d3dp.viewport.dwX;
	/* because of the negation, min and max Y switch */
	{
	    D3DVALUE tmp;
	    tmp = min_post[1];
	    min_post[1] = max_post[1];
	    max_post[1] = tmp;
	}
	min_post[1] = -(min_post[1] - 1) * This->d3dp.viewport.dwHeight / 2
	    + This->d3dp.viewport.dwY;
	max_post[1] = -(max_post[1] - 1) * This->d3dp.viewport.dwHeight / 2
	    + This->d3dp.viewport.dwY;

	if (min_post[0] < This->clip_status.minx)
	    This->clip_status.minx = min_post[0];
	if (max_post[0] > This->clip_status.maxx)
	    This->clip_status.maxx = max_post[0];
	if (min_post[1] < This->clip_status.miny)
	    This->clip_status.miny = min_post[1];
	if (max_post[1] > This->clip_status.maxy)
	    This->clip_status.maxy = max_post[1];
	if (min_post[2] < This->clip_status.minz)
	    This->clip_status.minz = min_post[2];
	if (max_post[2] > This->clip_status.maxz)
	    This->clip_status.maxz = max_post[2];
    }
    else
    {
	if (min_pre[0] < This->clip_status.minx)
	    This->clip_status.minx = min_pre[0];
	if (max_pre[0] > This->clip_status.maxx)
	    This->clip_status.maxx = max_pre[0];
	if (min_pre[1] < This->clip_status.miny)
	    This->clip_status.miny = min_pre[1];
	if (max_pre[1] > This->clip_status.maxy)
	    This->clip_status.maxy = max_pre[1];
	if (min_pre[3] < This->clip_status.minz)
	    This->clip_status.minz = min_pre[3];
	if (max_pre[3] > This->clip_status.maxz)
	    This->clip_status.maxz = max_pre[3];
    }
}

#define DRAW_PRIMITIVE \
    if (!This->d3dp.in_scene) { ERR("not in scene\n"); return D3DERR_SCENE_NOT_IN_SCENE; } \
    if (d3dptPrimType == D3DPT_POINTLIST) { \
	D3DHAL_DP2POINTS data; \
	cmd.bCommand = D3DDP2OP_POINTS; \
	cmd.u.wPrimitiveCount = 1; \
	data.wCount = dwVertexCount; \
	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(d3dptPrimType, dwVertexCount, &cmd)) \
	    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); \
    }

#define DRAW_INDEXED_PRIMITIVE \
    if (!This->d3dp.in_scene) { ERR("not in scene\n"); return D3DERR_SCENE_NOT_IN_SCENE; } \
    if (d3dptPrimType == D3DPT_POINTLIST) { \
        D3DHAL_DP2POINTS *data; \
        unsigned i, dsiz = dwIndexCount * sizeof(D3DHAL_DP2POINTS); \
        HRESULT hr; \
	cmd.bCommand = D3DDP2OP_POINTS; \
	cmd.u.wPrimitiveCount = dwIndexCount; \
	data = HeapAlloc(GetProcessHeap(), 0, dsiz); \
	for (i=0; i<dwIndexCount; i++) { \
	    data[i].wCount = 1; \
	    data[i].wVStart = lpwIndices[i]; \
	} \
	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(d3dptPrimType, dwIndexCount, &cmd)) \
	    return DDERR_INVALIDPARAMS; \
	data.wVStart = dwStartVertex; \
	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), lpwIndices, dwIndexCount*sizeof(WORD)); \
    }

#define DP2_START This->start_dp2(This->ip_ctx, D3DHALDP2_USERMEMVERTICES, \
				  d3dvtVertexType, lpvVertices, \
				  0, dwVertexCount, 0, 0);

#define UPDATE_CLIP_STATUS() \
	Main_Direct3DDevice_updateclipstatus(This, d3dvtVertexType, 0, \
					     lpvVertices, 0, dwVertexCount, \
					     NULL)

#define UPDATE_CLIP_STATUS_INDEXED() \
	Main_Direct3DDevice_updateclipstatus(This, d3dvtVertexType, 0, \
					     lpvVertices, 0, dwIndexCount, \
					     lpwIndices)

HRESULT WINAPI
Main_Direct3DDevice_DrawPrimitive(LPDIRECT3DDEVICE7 iface,
				  D3DPRIMITIVETYPE d3dptPrimType,
				  DWORD d3dvtVertexType,
				  LPVOID lpvVertices,
				  DWORD dwVertexCount,
				  DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd;

    TRACE("(%p)->(%d,%08lx,%p,%ld,%08lx)\n", This, d3dptPrimType, d3dvtVertexType,
					     lpvVertices, dwVertexCount, dwFlags);
    DRAW_PRIMITIVE
}

HRESULT WINAPI
Main_Direct3DDevice_DrawIndexedPrimitive(LPDIRECT3DDEVICE7 iface,
					 D3DPRIMITIVETYPE d3dptPrimType,
					 DWORD d3dvtVertexType,
					 LPVOID lpvVertices,
					 DWORD dwVertexCount,
					 LPWORD lpwIndices,
					 DWORD dwIndexCount,
					 DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd;
    DWORD dwStartVertex = 0;

    TRACE("(%p)->(%d,%08lx,%p,%ld,%p,%ld,%08lx)\n", This, d3dptPrimType, d3dvtVertexType,
						    lpvVertices, dwVertexCount,
						    lpwIndices, dwIndexCount, dwFlags);
    DRAW_INDEXED_PRIMITIVE
}

#undef DP2_START
#define DP2_START This->start_dp2_sd(This->ip_ctx, D3DHALDP2_USERMEMVERTICES, \
				     d3dvtVertexType, NULL, \
				     0, dwVertexCount, 0, 0, \
				     lpData);

#undef UPDATE_CLIP_STATUS
#define UPDATE_CLIP_STATUS() \
	Main_Direct3DDevice_updateclipstatus(This, d3dvtVertexType, \
					     lpData->position.dwStride, \
					     lpData->position.lpvData, 0, \
					     dwVertexCount, NULL)

#undef UPDATE_CLIP_STATUS_INDEXED
#define UPDATE_CLIP_STATUS_INDEXED() \
	Main_Direct3DDevice_updateclipstatus(This, d3dvtVertexType, \
					     lpData->position.dwStride, \
					     lpData->position.lpvData, 0, \
					     dwIndexCount, lpwIndices)

HRESULT WINAPI
Main_Direct3DDevice_DrawPrimitiveStrided(LPDIRECT3DDEVICE7 iface,
					 D3DPRIMITIVETYPE d3dptPrimType,
					 DWORD d3dvtVertexType,
					 LPD3DDRAWPRIMITIVESTRIDEDDATA lpData,
					 DWORD dwVertexCount,
					 DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd;

    TRACE("(%p)->(%d,%08lx,%p,%ld,%08lx)\n", This, d3dptPrimType, d3dvtVertexType,
					     lpData, dwVertexCount, dwFlags);
    DRAW_PRIMITIVE
}

HRESULT WINAPI
Main_Direct3DDevice_DrawIndexedPrimitiveStrided(LPDIRECT3DDEVICE7 iface,
						D3DPRIMITIVETYPE d3dptPrimType,
						DWORD d3dvtVertexType,
						LPD3DDRAWPRIMITIVESTRIDEDDATA lpData,
						DWORD dwVertexCount,
						LPWORD lpwIndices,
						DWORD dwIndexCount,
						DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd;
    DWORD dwStartVertex = 0;

    TRACE("(%p)->(%d,%08lx,%p,%ld,%p,%ld,%08lx)\n", This, d3dptPrimType, d3dvtVertexType,
						    lpData, dwVertexCount,
						    lpwIndices, dwIndexCount, dwFlags);
    DRAW_INDEXED_PRIMITIVE
}

#undef DP2_START
#define DP2_START This->start_dp2(This->ip_ctx, 0, \
				  d3dvtVertexType, &vbuf->t.lcl, \
				  dwStartVertex, dwVertexCount, 0, 0);
#define DPVB_INIT \
    IDirectDrawSurfaceImpl *vbuf = ICOM_OBJECT(IDirectDrawSurfaceImpl, \
					       IDirect3DVertexBuffer7, \
					       lpd3dVertexBuf); \
    DWORD d3dvtVertexType = vbuf->surface_desc.u4.dwFVF;

#undef UPDATE_CLIP_STATUS
#define UPDATE_CLIP_STATUS() \
	Main_Direct3DDevice_updateclipstatus(This, d3dvtVertexType, 0, \
					     (LPVOID)vbuf->t.gbl.fpVidMem, \
					     dwStartVertex, dwVertexCount, \
					     NULL)

#undef UPDATE_CLIP_STATUS_INDEXED
#define UPDATE_CLIP_STATUS_INDEXED() \
	Main_Direct3DDevice_updateclipstatus(This, d3dvtVertexType, 0, \
					     (LPVOID)vbuf->t.gbl.fpVidMem, \
					     dwStartVertex, dwIndexCount, \
					     lpwIndices)
HRESULT WINAPI
Main_Direct3DDevice_DrawPrimitiveVB(LPDIRECT3DDEVICE7 iface,
				    D3DPRIMITIVETYPE d3dptPrimType,
				    LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuf,
				    DWORD dwStartVertex,
				    DWORD dwVertexCount,
				    DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd;
    DPVB_INIT

    TRACE("(%p)->(%d,%p,%ld,%ld,%08lx)\n", This, d3dptPrimType, lpd3dVertexBuf,
					   dwStartVertex, dwVertexCount, dwFlags);
    DRAW_PRIMITIVE
}


HRESULT WINAPI
Main_Direct3DDevice_DrawIndexedPrimitiveVB(LPDIRECT3DDEVICE7 iface,
					   D3DPRIMITIVETYPE d3dptPrimType,
					   LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuf,
					   DWORD dwStartVertex,
					   DWORD dwVertexCount,
					   LPWORD lpwIndices,
					   DWORD dwIndexCount,
					   DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd;
    DPVB_INIT

    TRACE("(%p)->(%d,%p,%ld,%ld,%p,%ld,%08lx)\n", This, d3dptPrimType, lpd3dVertexBuf,
						  dwStartVertex, dwVertexCount, lpwIndices,
						  dwIndexCount, dwFlags);
    DRAW_INDEXED_PRIMITIVE
}

#undef DPVB_INIT
#undef DP2_START
#undef DRAW_INDEXED_PRIMITIVE
#undef DRAW_PRIMITIVE
#undef UPDATE_CLIP_STATUS
#undef UPDATE_CLIP_STATUS_INDEXED

HRESULT WINAPI
Main_Direct3DDevice_SetClipStatus(LPDIRECT3DDEVICE7 iface,
				  LPD3DCLIPSTATUS lpD3DClipStatus)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    /* this is poorly documented */

    TRACE("(%p)->(%p): dwFlags=%08lx\n", This, lpD3DClipStatus, lpD3DClipStatus->dwFlags);

    if (lpD3DClipStatus->dwFlags & D3DCLIPSTATUS_STATUS)
	This->clip_status.dwStatus = lpD3DClipStatus->dwStatus;

    if (lpD3DClipStatus->dwFlags & D3DCLIPSTATUS_EXTENTS2)
    {
	This->clip_status.minx = lpD3DClipStatus->minx;
	This->clip_status.maxx = lpD3DClipStatus->maxx;
	This->clip_status.miny = lpD3DClipStatus->miny;
	This->clip_status.maxy = lpD3DClipStatus->maxy;
	This->clip_status.minz = lpD3DClipStatus->minz;
	This->clip_status.maxz = lpD3DClipStatus->maxz;
    }

    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_GetClipStatus(LPDIRECT3DDEVICE7 iface,
				  LPD3DCLIPSTATUS lpD3DClipStatus)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    /* this is poorly documented */
    TRACE("(%p)->(%p): dwFlags=%08lx\n", This, lpD3DClipStatus, lpD3DClipStatus->dwFlags);

    if (lpD3DClipStatus->dwFlags & D3DCLIPSTATUS_STATUS)
	lpD3DClipStatus->dwStatus = This->clip_status.dwStatus;

    if (lpD3DClipStatus->dwFlags & D3DCLIPSTATUS_EXTENTS2)
    {
	lpD3DClipStatus->minx = This->clip_status.minx;
	lpD3DClipStatus->maxx = This->clip_status.maxx;
	lpD3DClipStatus->miny = This->clip_status.miny;
	lpD3DClipStatus->maxy = This->clip_status.maxy;
	lpD3DClipStatus->minz = This->clip_status.minz;
	lpD3DClipStatus->maxz = This->clip_status.maxz;
    }


    return D3D_OK;
}

          
HRESULT WINAPI
Main_Direct3DDevice_ComputeSphereVisibility(LPDIRECT3DDEVICE7 iface, LPD3DVECTOR lpCenters,
              LPD3DVALUE lpRadius, DWORD dwSpheres, DWORD dwFlags, LPDWORD lpdwReturnValues)
{
    /* This is quick implementation. All spheres are fully visible. */
    /* Should be computed more accurately to improve performance    */

    static BOOL warned=FALSE;
    if (!warned) {
        FIXME("Call not fully implemented\n");
	warned=TRUE;
    }
    
    if (lpdwReturnValues)
    {
      while(dwSpheres) {
        dwSpheres--;
        lpdwReturnValues[dwSpheres] = D3DVIS_INSIDE_FRUSTUM;     
      }
    }
                      
    return D3D_OK;
} 




HRESULT WINAPI
Main_Direct3DDevice_GetTexture(LPDIRECT3DDEVICE7 iface,
			       DWORD dwStage,
			       LPDIRECTDRAWSURFACE7* lplpTexture)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    *lplpTexture = This->state.tex[dwStage];
    TRACE("(%p)->(%ld,%p) => %p\n", This, dwStage, lplpTexture, *lplpTexture);
    if (*lplpTexture) IDirectDrawSurface7_AddRef(*lplpTexture);
    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_SetTexture(LPDIRECT3DDEVICE7 iface,
			       DWORD dwStage,
			       LPDIRECTDRAWSURFACE7 lpTexture)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    LPDIRECTDRAWSURFACE7 oldtex;
    IDirectDrawSurfaceImpl* tex = lpTexture ?
				  ICOM_OBJECT(IDirectDrawSurfaceImpl,
					      IDirectDrawSurface7,
					      lpTexture) :
				  NULL;
    /* 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 = tex ? (DWORD)&tex->t : 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 */
    if (lpTexture) IDirectDrawSurface7_AddRef(lpTexture);
    This->current->tex[dwStage] = lpTexture;
    This->current->dirty_tex |= 1<<dwStage;
    if (oldtex) IDirectDrawSurface7_Release(oldtex);

    if (!This->current->state_block)
	This->d3dp.texstagestate[dwStage][D3DTSS_TEXTUREMAP] = 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
Main_Direct3DDevice_GetTextureStageState(LPDIRECT3DDEVICE7 iface,
					 DWORD dwStage,
					 D3DTEXTURESTAGESTATETYPE d3dType,
					 LPDWORD lpdwState)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    if (d3dType >= 32) {
	WARN("out-of-range texture stage state %d\n", d3dType);
	return DDERR_INVALIDPARAMS;
    }
    *lpdwState = This->d3dp.texstagestate[dwStage][d3dType];
    TRACE("(%p)->(%ld,%d,%p) => %ld\n", This, dwStage, d3dType, lpdwState, *lpdwState);

    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_SetTextureStageState(LPDIRECT3DDEVICE7 iface,
					 DWORD dwStage,
					 D3DTEXTURESTAGESTATETYPE d3dType,
					 DWORD dwState)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXTURESTAGESTATE, 0, {1} };
    D3DHAL_DP2TEXTURESTAGESTATE stat;

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

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

    stat.wStage = dwStage;
    stat.TSState = d3dType;
    stat.dwValue = dwState;

    switch (d3dType) {
    case D3DTSS_ADDRESS:
	if (!This->current->state_block) {
	    This->d3dp.texstagestate[dwStage][D3DTSS_ADDRESSU] = dwState;
	    This->d3dp.texstagestate[dwStage][D3DTSS_ADDRESSV] = dwState;
	}
	stat.TSState = D3DTSS_ADDRESSU;
        This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
	stat.TSState = D3DTSS_ADDRESSV;
        This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
	stat.TSState = D3DTSS_ADDRESS;
	break;
    default:
	break;
    }

    /* 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;
    return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI
Main_Direct3DDevice_ValidateDevice(LPDIRECT3DDEVICE7 iface, LPDWORD lpdwPasses)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpdwPasses);
    return This->validate_device(This->ip_ctx, lpdwPasses);
}

HRESULT WINAPI
Main_Direct3DDevice_ApplyStateBlock(LPDIRECT3DDEVICE7 iface,
				    DWORD dwBlock)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DStateBlock* block;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
    D3DHAL_DP2STATESET data;
    HRESULT hr;

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

    block = (D3DStateBlock*)dwBlock;

    data.dwOperation = D3DHAL_STATESETEXECUTE;
    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)) {
	/* update current state to match new device state */
	unsigned u;
	for (u=0; u<8; u++) if (block->dirty_tex & (1<<u)) {
	    if (This->state.tex[u] != block->tex[u]) {
		if (block->tex[u]) IDirectDrawSurface7_AddRef(block->tex[u]);
		if (This->state.tex[u]) IDirectDrawSurface7_Release(This->state.tex[u]);
		This->state.tex[u] = block->tex[u];
	    }
	}
    }
    return hr;
}

HRESULT WINAPI
Main_Direct3DDevice_DeleteStateBlock(LPDIRECT3DDEVICE7 iface,
				    DWORD dwBlock)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    D3DStateBlock* block;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
    D3DHAL_DP2STATESET data;
    HRESULT hr;

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

    block = (D3DStateBlock*)dwBlock;

    data.dwOperation = D3DHAL_STATESETDELETE;
    data.dwParam = block->state_block;
    data.sbType = D3DSBT_ALL;
    hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
    if (FAILED(hr)) return hr;
    hr = This->free_state_block(This->ip_ctx, dwBlock);
    if (SUCCEEDED(hr)) {
	/* release referenced objects */
	unsigned u;
	for (u=0; u<8; u++)
	    if (block->tex[u]) IDirectDrawSurface7_Release(block->tex[u]);
	HeapFree(GetProcessHeap(), 0, block);
    }
    return hr;
}

static BOOL
get_next_mipmap(LPDIRECTDRAWSURFACE7 *ppSurf)
{
    static DDSCAPS2 mipmap_caps = { DDSCAPS_TEXTURE | DDSCAPS_MIPMAP };
    LPDIRECTDRAWSURFACE7 next;

    if (FAILED(IDirectDrawSurface7_GetAttachedSurface(*ppSurf, &mipmap_caps,
						      &next)))
	return FALSE;

    IDirectDrawSurface7_Release(*ppSurf);
    *ppSurf = next;

    return TRUE;
}

HRESULT WINAPI
Main_Direct3DDevice_Load(LPDIRECT3DDEVICE7 iface, LPDIRECTDRAWSURFACE7 lpDestTex,
			 LPPOINT lpDestPoint, LPDIRECTDRAWSURFACE7 lpSrcTex,
			 LPRECT lprcSrcRect, DWORD dwFlags)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    POINT ptDest;
    RECT rcSrc;

    TRACE("(%p)->(%p,%p,%p,%p,%08lx)\n", This,lpDestTex,lpDestPoint,lpSrcTex,lprcSrcRect,dwFlags);
    if (dwFlags) {
	FIXME("cubic maps not implemented\n");
	return DDERR_INVALIDPARAMS;
    }

    if (lpDestPoint != NULL)
	ptDest = *lpDestPoint;
    else
    {
	ptDest.x = 0;
	ptDest.y = 0;
    }

    if (lprcSrcRect != NULL)
	rcSrc = *lprcSrcRect;
    else
    {
	DDSURFACEDESC2 ddsd;

	DD_STRUCT_INIT(&ddsd);
	IDirectDrawSurface7_GetSurfaceDesc(lpSrcTex, &ddsd);

	rcSrc.top = 0;
	rcSrc.left = 0;
	rcSrc.right = ddsd.dwWidth;
	rcSrc.bottom = ddsd.dwHeight;
    }

    /* copy each mipmap */
    /* DX7 docs say "The destination texture surface can be a subset of the
     * mip-levels contained in the source texture." I take this to mean that
     * we may need to search through the mipmaps of lpSrcTex until we find
     * one that is the same size as lpDestTex. We don't do that yet.
     */
    IDirectDrawSurface7_AddRef(lpDestTex);
    IDirectDrawSurface7_AddRef(lpSrcTex);
    do
    {
	IDirectDrawSurface7_BltFast(lpDestTex, ptDest.x, ptDest.y,
				    lpSrcTex, &rcSrc,
				    DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

	ptDest.x /= 2;
	ptDest.y /= 2;

	rcSrc.top /= 2;
	rcSrc.left /= 2;
	rcSrc.right /= 2;
	rcSrc.bottom /= 2;
    }
    while (get_next_mipmap(&lpDestTex) && get_next_mipmap(&lpSrcTex));

    IDirectDrawSurface7_Release(lpDestTex);
    IDirectDrawSurface7_Release(lpSrcTex);

    /* XXX also copy cubemaps, palettes, colour keys */

    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice_LightEnable(LPDIRECT3DDEVICE7 iface,
				DWORD dwIndex, BOOL bEnable)
{
    ICOM_THIS(IDirect3DDeviceImpl, 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
Main_Direct3DDevice_GetLightEnable(LPDIRECT3DDEVICE7 iface,
				   DWORD dwIndex, LPBOOL lpbEnable)
{
    ICOM_THIS(IDirect3DDeviceImpl, 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
Main_Direct3DDevice_GetInfo(LPDIRECT3DDEVICE7 iface, DWORD DevInfoID,
			    LPVOID pDevInfoStruct, DWORD DevInfoStructSize)
{
    /* documented to return S_FALSE on retail builds of DirectX */
    TRACE("\n");
    return S_FALSE;
}

static ICOM_VTABLE(IDirect3DDevice7) Main_IDirect3DDevice7_VTable =
{
    Main_Direct3DDevice_QueryInterface,
    Main_Direct3DDevice_AddRef,
    Main_Direct3DDevice_Release,
    Main_Direct3DDevice_GetCaps,
    Main_Direct3DDevice_EnumTextureFormats,
    Main_Direct3DDevice_BeginScene,
    Main_Direct3DDevice_EndScene,
    Main_Direct3DDevice_GetDirect3D,
    Main_Direct3DDevice_SetRenderTarget,
    Main_Direct3DDevice_GetRenderTarget,
    Main_Direct3DDevice_Clear,
    Main_Direct3DDevice_SetTransform,
    Main_Direct3DDevice_GetTransform,
    Main_Direct3DDevice_SetViewport,
    Main_Direct3DDevice_MultiplyTransform,
    Main_Direct3DDevice_GetViewport,
    Main_Direct3DDevice_SetMaterial,
    Main_Direct3DDevice_GetMaterial,
    Main_Direct3DDevice_SetLight,
    Main_Direct3DDevice_GetLight,
    Main_Direct3DDevice_SetRenderState,
    Main_Direct3DDevice_GetRenderState,
    Main_Direct3DDevice_BeginStateBlock,
    Main_Direct3DDevice_EndStateBlock,
    Main_Direct3DDevice_PreLoad,
    Main_Direct3DDevice_DrawPrimitive,
    Main_Direct3DDevice_DrawIndexedPrimitive,
    Main_Direct3DDevice_SetClipStatus,
    Main_Direct3DDevice_GetClipStatus,
    Main_Direct3DDevice_DrawPrimitiveStrided,
    Main_Direct3DDevice_DrawIndexedPrimitiveStrided,
    Main_Direct3DDevice_DrawPrimitiveVB,
    Main_Direct3DDevice_DrawIndexedPrimitiveVB,
    Main_Direct3DDevice_ComputeSphereVisibility, 
    Main_Direct3DDevice_GetTexture,
    Main_Direct3DDevice_SetTexture,
    Main_Direct3DDevice_GetTextureStageState,
    Main_Direct3DDevice_SetTextureStageState,
    Main_Direct3DDevice_ValidateDevice,
    Main_Direct3DDevice_ApplyStateBlock,
    (void*)0xdead0025, /* CaptureStateBlock */
    Main_Direct3DDevice_DeleteStateBlock,
    (void*)0xdead0027, /* CreateStateBlock */
    Main_Direct3DDevice_Load,
    Main_Direct3DDevice_LightEnable,
    Main_Direct3DDevice_GetLightEnable,
    (void*)0xdead002B, /* SetClipPlane */
    (void*)0xdead002C, /* GetClipPlane */
    Main_Direct3DDevice_GetInfo
};

#define CONVERT7(pddgc) COM_INTERFACE_CAST(IDirect3DDeviceImpl,	\
					   IDirect3DDevice3,	\
					   IDirect3DDevice7,	\
					   (pddgc))

static HRESULT WINAPI
Main_Direct3DDevice3_QueryInterface(LPDIRECT3DDEVICE3 iface, REFIID riid,
				    LPVOID *ppObj)
{
    return IDirect3DDevice7_QueryInterface(CONVERT7(iface), riid, ppObj);
}

static ULONG WINAPI
Main_Direct3DDevice3_AddRef(LPDIRECT3DDEVICE3 iface)
{
    return IDirect3DDevice7_AddRef(CONVERT7(iface));
}

static ULONG WINAPI
Main_Direct3DDevice3_Release(LPDIRECT3DDEVICE3 iface)
{
    return IDirect3DDevice7_Release(CONVERT7(iface));
}

static HRESULT WINAPI
Main_Direct3DDevice3_GetCaps(LPDIRECT3DDEVICE3 iface,
			     LPD3DDEVICEDESC lpHAL,
			     LPD3DDEVICEDESC lpHEL)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice3, iface);
    LPDDRAWI_DIRECTDRAW_GBL dd_gbl = This->ddraw_owner->local.lpGbl;
    LPD3DHAL_GLOBALDRIVERDATA data = (LPD3DHAL_GLOBALDRIVERDATA)dd_gbl->lpD3DGlobalDriverData;
    LPD3DHAL_D3DEXTENDEDCAPS extcaps = (LPD3DHAL_D3DEXTENDEDCAPS)dd_gbl->lpD3DExtendedCaps;
    D3DDEVICEDESC desc;

    TRACE("(%p)->(%p,%p)\n", This, lpHAL, lpHEL);
    Direct3D3_MakeDeviceDesc(&desc, &data->hwCaps, extcaps);

    if (lpHAL) DD_STRUCT_COPY_BYSIZE(lpHAL, &desc);
    if (lpHEL) DD_STRUCT_COPY_BYSIZE(lpHEL, &desc);

    return D3D_OK;
}

static HRESULT WINAPI
Main_Direct3DDevice3_AddViewport(LPDIRECT3DDEVICE3 iface,
				 LPDIRECT3DVIEWPORT3 lpView)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice3, iface);
    IDirect3DViewportImpl* view = ICOM_OBJECT(IDirect3DViewportImpl,
					      IDirect3DViewport3,
					      lpView);

    TRACE("(%p)->(%p)\n", This, lpView);
    if (view->dev_owner == This) return DDERR_INVALIDPARAMS;
    if (view->dev_owner != NULL) return DDERR_INVALIDPARAMS; /* hmm */
    /* FIXME: attach to linked list */
    view->dev_owner = This;
    return D3D_OK;
}

static HRESULT WINAPI
Main_Direct3DDevice3_DeleteViewport(LPDIRECT3DDEVICE3 iface,
                                 LPDIRECT3DVIEWPORT3 lpView)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice3, iface);
    IDirect3DViewportImpl* view = ICOM_OBJECT(IDirect3DViewportImpl,
                                              IDirect3DViewport3,
                                              lpView);

    TRACE("(%p)->(%p)\n", This, lpView);
    if (view->dev_owner == NULL) return DDERR_INVALIDPARAMS; /* hmm */

    if (This->view == view) Direct3DViewport_Unbind(view, This);

    /* FIXME: remove from linked list */
    view->dev_owner = NULL;
    return D3D_OK;
}

static HRESULT WINAPI
Main_Direct3DDevice3_EnumTextureFormats(LPDIRECT3DDEVICE3 iface,
					LPD3DENUMPIXELFORMATSCALLBACK lpCB,
					LPVOID lpContext)
{
    return IDirect3DDevice7_EnumTextureFormats(CONVERT7(iface),
					       lpCB, lpContext);
}

static HRESULT WINAPI
Main_Direct3DDevice3_BeginScene(LPDIRECT3DDEVICE3 iface)
{
    return IDirect3DDevice7_BeginScene(CONVERT7(iface));
}

static HRESULT WINAPI
Main_Direct3DDevice3_EndScene(LPDIRECT3DDEVICE3 iface)
{
    return IDirect3DDevice7_EndScene(CONVERT7(iface));
}

HRESULT WINAPI
Main_Direct3DDevice3_GetDirect3D(LPDIRECT3DDEVICE3 iface, LPDIRECT3D3*lplpDirect3D)
{
    ICOM_THIS(IDirect3DDeviceImpl, iface);
    TRACE("(%p)->(%p)\n", This, lplpDirect3D);
    *lplpDirect3D = ICOM_INTERFACE(This->ddraw_owner, IDirect3D3);
    IDirect3D3_AddRef(*lplpDirect3D);
    return D3D_OK;
}


static HRESULT WINAPI
Main_Direct3DDevice3_SetCurrentViewport(LPDIRECT3DDEVICE3 iface,
					LPDIRECT3DVIEWPORT3 lpView)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice3, iface);
    IDirect3DViewportImpl* view = ICOM_OBJECT(IDirect3DViewportImpl,
					      IDirect3DViewport3,
					      lpView);

    TRACE("(%p)->(%p)\n", This, lpView);
    if (view->dev_owner != This) return D3DERR_INVALIDCURRENTVIEWPORT;

    if (This->view) Direct3DViewport_Unbind(This->view, This);

    This->view = view;
    Direct3DViewport_Bind(view, This);

    return D3D_OK;
}

HRESULT WINAPI
Main_Direct3DDevice3_SetRenderTarget(LPDIRECT3DDEVICE3 iface, LPDIRECTDRAWSURFACE4 lpDDS, DWORD dwFlags)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice3, iface);
    HRESULT hr;

    hr = IDirect3DDevice7_SetRenderTarget(CONVERT7(iface), (LPDIRECTDRAWSURFACE7)lpDDS, dwFlags);
    if (FAILED(hr)) return hr;

    /* Every version except DX7 does this. */
    if (This->view)
	hr = IDirect3DDevice7_SetViewport(CONVERT7(iface), &This->view->v);

    return hr;
}

HRESULT WINAPI
Main_Direct3DDevice3_GetRenderTarget(LPDIRECT3DDEVICE3 iface,
				    LPDIRECTDRAWSURFACE4* lplpDDS)
{
    /* there's no IDirectDrawSurface4 vtable */
    return IDirect3DDevice7_GetRenderTarget(CONVERT7(iface), (LPDIRECTDRAWSURFACE7*)lplpDDS);
}

static HRESULT WINAPI
Main_Direct3DDevice3_SetRenderState(LPDIRECT3DDEVICE3 iface,
				    D3DRENDERSTATETYPE d3dType,
				    DWORD dwState)
{
    static DWORD magfilter[3] = { 0, 1, 2 };
    static DWORD minfilter[7] = { 0, 1, 2, 1, 1, 2, 2 };
    static DWORD mipfilter[7] = { 0, 1, 1, 2, 3, 2, 3 };
    HRESULT hr = DD_OK;
    DWORD tmp;

    switch (d3dType) {
    case D3DRENDERSTATE_TEXTUREHANDLE:
	{
	    IDirectDrawSurfaceImpl* tex = (IDirectDrawSurfaceImpl*)dwState;
	    LPDIRECTDRAWSURFACE7 tex7;
	    DWORD pf = 0;
	    if (tex) {
		pf = tex->surface_desc.u4.ddpfPixelFormat.dwFlags;
		tex7 = ICOM_INTERFACE(tex, IDirectDrawSurface7);
	    } else tex7 = NULL;
	    hr = IDirect3DDevice7_SetTexture(CONVERT7(iface), 0, tex7);
	    if (FAILED(hr)) break;
	    /* fix up TEXTUREMAPBLEND op if it's MODULATE */
	    hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
						       D3DTSS_COLOROP, &tmp);
	    if (FAILED(hr)) break;
	    if (tmp == D3DTOP_MODULATE) {
		hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP, &tmp);
		if (FAILED(hr)) break;
		if (tmp == D3DTOP_MODULATE) break; /* MODULATEALPHA */
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP,
							   (pf & DDPF_ALPHAPIXELS)
							   ? D3DTOP_SELECTARG1
							   : D3DTOP_SELECTARG2);
	    }
	}
	break;
    case D3DRENDERSTATE_TEXTUREADDRESS:
	FIXME("need to convert render state %d <= %ld\n", d3dType, dwState);
	break;
    case D3DRENDERSTATE_WRAPU:
	hr = IDirect3DDevice7_GetRenderState(CONVERT7(iface),
					     D3DRENDERSTATE_WRAP0,
					     &tmp);
	if (FAILED(hr)) break;
	if (dwState) tmp |= D3DWRAP_U;
	else tmp &= ~D3DWRAP_U;
	hr = IDirect3DDevice7_SetRenderState(CONVERT7(iface),
					     D3DRENDERSTATE_WRAP0,
					     tmp);
	break;
    case D3DRENDERSTATE_WRAPV:
	hr = IDirect3DDevice7_GetRenderState(CONVERT7(iface),
					     D3DRENDERSTATE_WRAP0,
					     &tmp);
	if (FAILED(hr)) break;
	if (dwState) tmp |= D3DWRAP_V;
	else tmp &= ~D3DWRAP_V;
	hr = IDirect3DDevice7_SetRenderState(CONVERT7(iface),
					     D3DRENDERSTATE_WRAP0,
					     tmp);
	break;
    case D3DRENDERSTATE_MONOENABLE:
    case D3DRENDERSTATE_ROP2:
    case D3DRENDERSTATE_PLANEMASK:
	FIXME("need to convert render state %d <= %ld\n", d3dType, dwState);
	break;
    case D3DRENDERSTATE_TEXTUREMAG:
	hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
						   D3DTSS_MAGFILTER,
						   magfilter[dwState]);
	break;
    case D3DRENDERSTATE_TEXTUREMIN:
	hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
						   D3DTSS_MIPFILTER,
						   mipfilter[dwState]);
	if (FAILED(hr)) break;
	hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
						   D3DTSS_MINFILTER,
						   minfilter[dwState]);
	break;
    case D3DRENDERSTATE_TEXTUREMAPBLEND:
	{
	    LPDIRECTDRAWSURFACE7 tex7;
	    DWORD pf = 0;
	    /* get current texture */
	    hr = IDirect3DDevice7_GetTexture(CONVERT7(iface), 0, &tex7);
	    if (FAILED(hr)) break;
	    if (tex7) {
		IDirectDrawSurfaceImpl* tex = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7, tex7);
		pf = tex->surface_desc.u4.ddpfPixelFormat.dwFlags;
		IDirectDrawSurface7_Release(tex7);
	    }
	    /* We'll assume that colorargs and alphaargs for stage 0 is left
	     * at their defaults: arg1 = texture, arg2 = current (diffuse) */
	    switch (dwState) {
	    case D3DTBLEND_DECAL:
	    case D3DTBLEND_COPY:
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_COLOROP,
							   D3DTOP_SELECTARG1);
		if (FAILED(hr)) break;
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP,
							   D3DTOP_SELECTARG1);
		break;
	    case D3DTBLEND_MODULATE:
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_COLOROP,
							   D3DTOP_MODULATE);
		if (FAILED(hr)) break;
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP,
							   (pf & DDPF_ALPHAPIXELS)
							   ? D3DTOP_SELECTARG1
							   : D3DTOP_SELECTARG2);
		break;
	    case D3DTBLEND_DECALALPHA:
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_COLOROP,
							   D3DTOP_BLENDTEXTUREALPHA);
		if (FAILED(hr)) break;
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP,
							   D3DTOP_SELECTARG2);
		break;
	    case D3DTBLEND_MODULATEALPHA:
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_COLOROP,
							   D3DTOP_MODULATE);
		if (FAILED(hr)) break;
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP,
							   D3DTOP_MODULATE);
		break;
	    case D3DTBLEND_ADD:
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_COLOROP,
							   D3DTOP_ADD);
		if (FAILED(hr)) break;
		hr = IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP,
							   D3DTOP_SELECTARG2);
		break;
	    case D3DTBLEND_DECALMASK:
	    case D3DTBLEND_MODULATEMASK:
	    default:
		FIXME("unsupported TEXTUREMAPBLEND mode %ld\n", dwState);
		break;
	    }
	}
	break;
    case D3DRENDERSTATE_SUBPIXEL:
    case D3DRENDERSTATE_SUBPIXELX:
    case D3DRENDERSTATE_STIPPLEENABLE:
    case D3DRENDERSTATE_BORDERCOLOR:
    case D3DRENDERSTATE_TEXTUREADDRESSU:
    case D3DRENDERSTATE_TEXTUREADDRESSV:
    case D3DRENDERSTATE_MIPMAPLODBIAS:
    case D3DRENDERSTATE_ANISOTROPY:
	FIXME("need to convert render state %d <= %ld\n", d3dType, dwState);
	break;
    case D3DRENDERSTATE_FLUSHBATCH:
	/* I don't think we need to do anything */
	break;
    case D3DRENDERSTATE_TRANSLUCENTSORTINDEPENDENT:
    /* and STIPPLEPATTERN */
	FIXME("need to convert render state %d <= %ld\n", d3dType, dwState);
	break;
    default:
	hr = IDirect3DDevice7_SetRenderState(CONVERT7(iface),
					     d3dType, dwState);
    }
    return hr;
}

static HRESULT WINAPI
Main_Direct3DDevice3_GetRenderState(LPDIRECT3DDEVICE3 iface,
				    D3DRENDERSTATETYPE d3dType,
				    LPDWORD lpdwRenderState)
{
    static DWORD magfilter[3] = { 0, 1, 2 };
    static DWORD minfilter[3][4] = { { 0, 0, 0, 0 },
				     { 0, 1, 3, 4 },
				     { 0, 2, 5, 6 } };
    HRESULT hr = DD_OK;
    DWORD tmp;

    switch (d3dType) {
    case D3DRENDERSTATE_TEXTUREHANDLE:
	{
	    IDirectDrawSurfaceImpl* tex;
	    LPDIRECTDRAWSURFACE7 tex7;
	    hr = IDirect3DDevice7_GetTexture(CONVERT7(iface), 0, &tex7);
	    if (FAILED(hr)) break;
	    tex = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7, tex7);
	    if (tex7) IDirectDrawSurface7_Release(tex7);
	    *lpdwRenderState = (DWORD)tex;
	}
	break;
    case D3DRENDERSTATE_TEXTUREADDRESS:
	FIXME("need to convert render state %d\n", d3dType);
	break;
    case D3DRENDERSTATE_WRAPU:
	hr = IDirect3DDevice7_GetRenderState(CONVERT7(iface),
					     D3DRENDERSTATE_WRAP0,
					     &tmp);
	if (FAILED(hr)) return hr;
	*lpdwRenderState = (tmp & D3DWRAP_U) != 0;
	break;
    case D3DRENDERSTATE_WRAPV:
	hr = IDirect3DDevice7_GetRenderState(CONVERT7(iface),
					     D3DRENDERSTATE_WRAP0,
					     &tmp);
	if (FAILED(hr)) return hr;
	*lpdwRenderState = (tmp & D3DWRAP_V) != 0;
	break;
    case D3DRENDERSTATE_MONOENABLE:
    case D3DRENDERSTATE_ROP2:
    case D3DRENDERSTATE_PLANEMASK:
	FIXME("need to convert render state %d\n", d3dType);
	break;
    case D3DRENDERSTATE_TEXTUREMAG:
	{
	    DWORD mag;
	    hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
						       D3DTSS_MAGFILTER, &mag);
	    if (FAILED(hr)) break;
	    *lpdwRenderState = magfilter[mag];
	}
	break;
    case D3DRENDERSTATE_TEXTUREMIN:
	{
	    DWORD min, mip;
	    hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
						       D3DTSS_MINFILTER, &min);
	    if (FAILED(hr)) break;
	    hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
						       D3DTSS_MIPFILTER, &mip);
	    if (FAILED(hr)) break;
	    *lpdwRenderState = minfilter[min][mip];
	}
	break;
    case D3DRENDERSTATE_TEXTUREMAPBLEND:
	{
	    hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
						       D3DTSS_COLOROP, &tmp);
	    if (FAILED(hr)) break;
	    switch (tmp) {
	    case D3DTOP_SELECTARG1:
		*lpdwRenderState = D3DTBLEND_DECAL;
		break;
	    case D3DTOP_MODULATE:
		hr = IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), 0,
							   D3DTSS_ALPHAOP, &tmp);
		if (tmp == D3DTOP_MODULATE) *lpdwRenderState = D3DTBLEND_MODULATEALPHA;
		else *lpdwRenderState = D3DTBLEND_MODULATE;
		break;
	    case D3DTOP_ADD:
		*lpdwRenderState = D3DTBLEND_ADD;
		break;
	    case D3DTOP_BLENDTEXTUREALPHA:
		*lpdwRenderState = D3DTBLEND_DECALALPHA;
		break;
	    default:
		FIXME("unsupported colorop %ld for TEXTUREMAPBLEND\n", tmp);
		*lpdwRenderState = 0;
		break;
	    }
	}
	break;
    case D3DRENDERSTATE_SUBPIXEL:
    case D3DRENDERSTATE_SUBPIXELX:
    case D3DRENDERSTATE_STIPPLEENABLE:
    case D3DRENDERSTATE_BORDERCOLOR:
    case D3DRENDERSTATE_TEXTUREADDRESSU:
    case D3DRENDERSTATE_TEXTUREADDRESSV:
    case D3DRENDERSTATE_MIPMAPLODBIAS:
    case D3DRENDERSTATE_ANISOTROPY:
	FIXME("need to convert render state %d\n", d3dType);
	break;
    case D3DRENDERSTATE_FLUSHBATCH:
	/* apps shouldn't do this, but why not "handle" it... */
	*lpdwRenderState = 0;
	break;
    case D3DRENDERSTATE_TRANSLUCENTSORTINDEPENDENT:
    /* and STIPPLEPATTERN */
	FIXME("need to convert render state %d\n", d3dType);
	break;
    default:
	hr = IDirect3DDevice7_GetRenderState(CONVERT7(iface),
					     d3dType, lpdwRenderState);
    }
    return hr;
}

static HRESULT WINAPI
Main_Direct3DDevice3_GetLightState(LPDIRECT3DDEVICE3 iface,
				   D3DLIGHTSTATETYPE dwLightStateType,
				   LPDWORD lpdwLightState)
{
    *lpdwLightState = 0;
    return D3D_OK;
}

static HRESULT WINAPI
Main_Direct3DDevice3_SetLightState(LPDIRECT3DDEVICE3 iface,
				   D3DLIGHTSTATETYPE dlsType,
				   DWORD dwState)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice3, iface);

    DWORD d3dType;
    TRACE("(%p)->(%d,%ld)\n",iface,dlsType,dwState);
    switch (dlsType) {
    case D3DLIGHTSTATE_MATERIAL:
    {
	IDirect3DMaterialImpl* material = (IDirect3DMaterialImpl*)dwState;

	if (material != This->material_handle)
	{
	    if (This->material_handle)
	    {
		This->material_handle->dev_owner = NULL;
		IDirect3DMaterial_Release((IDirect3DMaterial *)This->material_handle);
	    }

	    This->material_handle = material;
	    if (material)
	    {
		IDirect3DMaterial_AddRef((IDirect3DMaterial *)material);
		material->dev_owner = This;

		Direct3DMaterial_update(material);
	    }
	    else
	    {
		IDirect3DDevice7_SetRenderState(iface, D3DRENDERSTATE_LIGHTING,
						FALSE);
	    }
	}

	return D3D_OK;
    }

    case D3DLIGHTSTATE_AMBIENT:
	d3dType = D3DRENDERSTATE_AMBIENT;
	break;
    case D3DLIGHTSTATE_COLORMODEL:
	/* we only support D3DCOLOR_RGB */
	if (dwState != D3DCOLOR_RGB) {
	    ERR("unhandled D3DLIGHTSTATE_COLORMODEL %ld\n", dwState);
	    return DDERR_INVALIDPARAMS;
	}
	return D3D_OK;
    case D3DLIGHTSTATE_FOGMODE:
	d3dType = D3DRENDERSTATE_FOGVERTEXMODE;
	break;
    /* these may conflict with D3DRENDERSTATE_FOGTABLExxx
     * if a game sets both (and with different values), let's hope not */
    case D3DLIGHTSTATE_FOGSTART:
	d3dType = D3DRENDERSTATE_FOGSTART;
	break;
    case D3DLIGHTSTATE_FOGEND:
	d3dType = D3DRENDERSTATE_FOGEND;
	break;
    case D3DLIGHTSTATE_FOGDENSITY:
	d3dType = D3DRENDERSTATE_FOGDENSITY;
	break;
    case D3DLIGHTSTATE_COLORVERTEX:
	d3dType = D3DRENDERSTATE_COLORVERTEX;
	break;
    default:
	ERR("unknown light state %d\n", dlsType);
	return DDERR_INVALIDPARAMS;
    }
    return IDirect3DDevice7_SetRenderState(CONVERT7(iface), d3dType, dwState);
}

static HRESULT WINAPI
Main_Direct3DDevice3_SetTransform(LPDIRECT3DDEVICE3 iface,
				  D3DTRANSFORMSTATETYPE dtstType,
				  LPD3DMATRIX lpD3DMatrix)
{
    return IDirect3DDevice7_SetTransform(CONVERT7(iface),
					 dtstType, lpD3DMatrix);
}

static HRESULT WINAPI
Main_Direct3DDevice3_GetTransform(LPDIRECT3DDEVICE3 iface,
				  D3DTRANSFORMSTATETYPE dtstType,
				  LPD3DMATRIX lpD3DMatrix)
{
    return IDirect3DDevice7_GetTransform(CONVERT7(iface),
					 dtstType, lpD3DMatrix);
}

static HRESULT WINAPI
Main_Direct3DDevice3_MultiplyTransform(LPDIRECT3DDEVICE3 iface,
				  D3DTRANSFORMSTATETYPE dtstType,
				  LPD3DMATRIX lpD3DMatrix)
{
    return IDirect3DDevice7_MultiplyTransform(CONVERT7(iface),
					 dtstType, lpD3DMatrix);
}

static HRESULT WINAPI
Main_Direct3DDevice3_DrawPrimitive(LPDIRECT3DDEVICE3 iface,
				   D3DPRIMITIVETYPE d3dptPrimType,
				   DWORD d3dvtVertexType,
				   LPVOID lpvVertices,
				   DWORD dwVertexCount,
				   DWORD dwFlags)
{
    return IDirect3DDevice7_DrawPrimitive(CONVERT7(iface), d3dptPrimType,
					  d3dvtVertexType, lpvVertices,
					  dwVertexCount, dwFlags);
}

static HRESULT WINAPI
Main_Direct3DDevice3_DrawIndexedPrimitive(LPDIRECT3DDEVICE3 iface,
					  D3DPRIMITIVETYPE d3dptPrimType,
					  DWORD d3dvtVertexType,
					  LPVOID lpvVertices,
					  DWORD dwVertexCount,
					  LPWORD lpwIndices,
					  DWORD dwIndexCount,
					  DWORD dwFlags)
{
    return IDirect3DDevice7_DrawIndexedPrimitive(CONVERT7(iface), d3dptPrimType,
						 d3dvtVertexType, lpvVertices,
						 dwVertexCount, lpwIndices,
						 dwIndexCount, dwFlags);
}

static HRESULT WINAPI
Main_Direct3DDevice3_SetClipStatus(LPDIRECT3DDEVICE3 iface,
				   LPD3DCLIPSTATUS lpD3DClipStatus)
{
    return IDirect3DDevice7_SetClipStatus(CONVERT7(iface), lpD3DClipStatus);
}

static HRESULT WINAPI
Main_Direct3DDevice3_GetClipStatus(LPDIRECT3DDEVICE3 iface,
				   LPD3DCLIPSTATUS lpD3DClipStatus)
{
    return IDirect3DDevice7_GetClipStatus(CONVERT7(iface), lpD3DClipStatus);
}

static HRESULT WINAPI
Main_Direct3DDevice3_DrawPrimitiveStrided(LPDIRECT3DDEVICE3 iface,
					  D3DPRIMITIVETYPE d3dptPrimType,
					  DWORD d3dvtVertexType,
					  LPD3DDRAWPRIMITIVESTRIDEDDATA lpData,
					  DWORD dwVertexCount,
					  DWORD dwFlags)
{
    return IDirect3DDevice7_DrawPrimitiveStrided(CONVERT7(iface), d3dptPrimType,
					         d3dvtVertexType, lpData,
					         dwVertexCount, dwFlags);
}

static HRESULT WINAPI
Main_Direct3DDevice3_DrawIndexedPrimitiveStrided(LPDIRECT3DDEVICE3 iface,
						 D3DPRIMITIVETYPE d3dptPrimType,
						 DWORD d3dvtVertexType,
						 LPD3DDRAWPRIMITIVESTRIDEDDATA lpData,
						 DWORD dwVertexCount,
						 LPWORD lpwIndices,
						 DWORD dwIndexCount,
						 DWORD dwFlags)
{
    return IDirect3DDevice7_DrawIndexedPrimitiveStrided(CONVERT7(iface), d3dptPrimType,
						        d3dvtVertexType, lpData,
						        dwVertexCount, lpwIndices,
						        dwIndexCount, dwFlags);
}

#define CONVERTVB7(pd3dvb) COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,	\
					      IDirect3DVertexBuffer,	\
					      IDirect3DVertexBuffer7,	\
					      (pd3dvb))

static HRESULT WINAPI
Main_Direct3DDevice3_DrawPrimitiveVB(LPDIRECT3DDEVICE3 iface,
				     D3DPRIMITIVETYPE d3dptPrimType,
				     LPDIRECT3DVERTEXBUFFER lpd3dVertexBuf,
				     DWORD dwStartVertex,
				     DWORD dwVertexCount,
				     DWORD dwFlags)
{

    return IDirect3DDevice7_DrawPrimitiveVB(CONVERT7(iface), d3dptPrimType,
					    CONVERTVB7(lpd3dVertexBuf), dwStartVertex,
					    dwVertexCount, dwFlags);
}

static HRESULT WINAPI
Main_Direct3DDevice3_DrawIndexedPrimitiveVB(LPDIRECT3DDEVICE3 iface,
					    D3DPRIMITIVETYPE d3dptPrimType,
					    LPDIRECT3DVERTEXBUFFER lpd3dVertexBuf,
					    LPWORD lpwIndices,
					    DWORD dwIndexCount,
					    DWORD dwFlags)
{
    DWORD dwVertexCount;

    /* Calculate the number or verticies in the VB - this isn't saved anywhere (gah!) */
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DVertexBuffer, lpd3dVertexBuf);
    dwVertexCount = This->surface_desc.u1.dwLinearSize / D3D_FVF2Size(This->surface_desc.u4.dwFVF);
    return IDirect3DDevice7_DrawIndexedPrimitiveVB(CONVERT7(iface), d3dptPrimType,
						   CONVERTVB7(lpd3dVertexBuf),
						   0, dwVertexCount,
						   lpwIndices, dwIndexCount, dwFlags);
}

#undef CONVERTVB7

static HRESULT WINAPI
Main_Direct3DDevice3_ComputeSphereVisibility(LPDIRECT3DDEVICE3 iface, LPD3DVECTOR lpCenters,
              LPD3DVALUE  lpRadius, DWORD dwSpheres, DWORD dwFlags,LPDWORD lpdwReturnValues)
{
     return IDirect3DDevice7_ComputeSphereVisibility(CONVERT7(iface),lpCenters,
                                                     lpRadius,dwSpheres,
                                                     dwFlags,lpdwReturnValues);
}


static HRESULT WINAPI
Main_Direct3DDevice3_GetTexture(LPDIRECT3DDEVICE3 iface,
				DWORD dwStage,
				LPDIRECT3DTEXTURE2 *lplpTexture)
{
    LPDIRECTDRAWSURFACE7 lpSurface;
    HRESULT hr;
    hr = IDirect3DDevice7_GetTexture(CONVERT7(iface), dwStage,
				     &lpSurface);
    *lplpTexture = COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
				      IDirectDrawSurface7,
				      IDirect3DTexture2,
				      lpSurface);
    return hr;
}

static HRESULT WINAPI
Main_Direct3DDevice3_SetTexture(LPDIRECT3DDEVICE3 iface,
				DWORD dwStage,
				LPDIRECT3DTEXTURE2 lpTexture)
{
    return IDirect3DDevice7_SetTexture(CONVERT7(iface), dwStage,
				       COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
							  IDirect3DTexture2,
							  IDirectDrawSurface7,
							  lpTexture));
}

static HRESULT WINAPI
Main_Direct3DDevice3_GetTextureStageState(LPDIRECT3DDEVICE3 iface,
					  DWORD dwStage,
					  D3DTEXTURESTAGESTATETYPE d3dType,
					  LPDWORD lpdwState)
{
    return IDirect3DDevice7_GetTextureStageState(CONVERT7(iface), dwStage,
						 d3dType, lpdwState);
}

static HRESULT WINAPI
Main_Direct3DDevice3_SetTextureStageState(LPDIRECT3DDEVICE3 iface,
					  DWORD dwStage,
					  D3DTEXTURESTAGESTATETYPE d3dType,
					  DWORD dwState)
{
    return IDirect3DDevice7_SetTextureStageState(CONVERT7(iface), dwStage,
						 d3dType, dwState);
}

static HRESULT WINAPI
Main_Direct3DDevice3_ValidateDevice(LPDIRECT3DDEVICE3 iface,
				    LPDWORD lpdwPasses)
{
    return IDirect3DDevice7_ValidateDevice(CONVERT7(iface), lpdwPasses);
}


#undef CONVERT7

static ICOM_VTABLE(IDirect3DDevice3) Main_IDirect3DDevice3_VTable =
{
    Main_Direct3DDevice3_QueryInterface,
    Main_Direct3DDevice3_AddRef,
    Main_Direct3DDevice3_Release,
    Main_Direct3DDevice3_GetCaps,
    (void*)0xdead1001, /* GetStats */
    Main_Direct3DDevice3_AddViewport,
    Main_Direct3DDevice3_DeleteViewport,
    (void*)0xdead1004, /* NextViewport */
    Main_Direct3DDevice3_EnumTextureFormats,
    Main_Direct3DDevice3_BeginScene,
    Main_Direct3DDevice3_EndScene,
    Main_Direct3DDevice3_GetDirect3D,
    Main_Direct3DDevice3_SetCurrentViewport,
    (void*)0xdead100A, /* GetCurrentViewport */
    Main_Direct3DDevice3_SetRenderTarget,
    Main_Direct3DDevice3_GetRenderTarget,
    (void*)0xdead100D, /* Begin */
    (void*)0xdead100E, /* BeginIndexed */
    (void*)0xdead100F, /* Vertex */
    (void*)0xdead1010, /* Index */
    (void*)0xdead1011, /* End */
    Main_Direct3DDevice3_GetRenderState,
    Main_Direct3DDevice3_SetRenderState,
    Main_Direct3DDevice3_GetLightState,
    Main_Direct3DDevice3_SetLightState,
    Main_Direct3DDevice3_SetTransform,
    Main_Direct3DDevice3_GetTransform,
    Main_Direct3DDevice3_MultiplyTransform,
    Main_Direct3DDevice3_DrawPrimitive,
    Main_Direct3DDevice3_DrawIndexedPrimitive,
    Main_Direct3DDevice3_SetClipStatus,
    Main_Direct3DDevice3_GetClipStatus,
    Main_Direct3DDevice3_DrawPrimitiveStrided,
    Main_Direct3DDevice3_DrawIndexedPrimitiveStrided,
    Main_Direct3DDevice3_DrawPrimitiveVB,
    Main_Direct3DDevice3_DrawIndexedPrimitiveVB,
    Main_Direct3DDevice3_ComputeSphereVisibility,
    Main_Direct3DDevice3_GetTexture,
    Main_Direct3DDevice3_SetTexture,
    Main_Direct3DDevice3_GetTextureStageState,
    Main_Direct3DDevice3_SetTextureStageState,
    Main_Direct3DDevice3_ValidateDevice
};

#define CONVERT7(pddgc) COM_INTERFACE_CAST(IDirect3DDeviceImpl,	\
					   IDirect3DDevice2,	\
					   IDirect3DDevice7,	\
					   (pddgc))
#define CONVERT3(pddgc) COM_INTERFACE_CAST(IDirect3DDeviceImpl,	\
					   IDirect3DDevice2,	\
					   IDirect3DDevice3,	\
					   (pddgc))


static HRESULT WINAPI
Main_Direct3DDevice2_QueryInterface(LPDIRECT3DDEVICE2 iface, REFIID riid,
				    LPVOID *ppObj)
{
    return IDirect3DDevice7_QueryInterface(CONVERT7(iface), riid, ppObj);
}

static ULONG WINAPI
Main_Direct3DDevice2_AddRef(LPDIRECT3DDEVICE2 iface)
{
    return IDirect3DDevice7_AddRef(CONVERT7(iface));
}

static ULONG WINAPI
Main_Direct3DDevice2_Release(LPDIRECT3DDEVICE2 iface)
{
    return IDirect3DDevice7_Release(CONVERT7(iface));
}

static HRESULT WINAPI
Main_Direct3DDevice2_GetCaps(LPDIRECT3DDEVICE2 iface,
			     LPD3DDEVICEDESC lpHAL,
			     LPD3DDEVICEDESC lpHEL)
{
    return IDirect3DDevice3_GetCaps(CONVERT3(iface), lpHAL, lpHEL);
}

static HRESULT WINAPI
Main_Direct3DDevice2_AddViewport(LPDIRECT3DDEVICE2 iface,
				 LPDIRECT3DVIEWPORT2 lpView)
{
    /* IDirect3DViewportImpl has only one vtable */
    return IDirect3DDevice3_AddViewport(CONVERT3(iface),
					(LPDIRECT3DVIEWPORT3)lpView);
}

static HRESULT WINAPI
Main_Direct3DDevice2_DeleteViewport(LPDIRECT3DDEVICE2 iface,
				    LPDIRECT3DVIEWPORT2 lpView)
{
    /* IDirect3DViewportImpl has only one vtable */
    return IDirect3DDevice3_DeleteViewport(CONVERT3(iface),
                                        (LPDIRECT3DVIEWPORT3)lpView);
}


static HRESULT WINAPI
Main_Direct3DDevice2_EnumTextureFormats(LPDIRECT3DDEVICE2 iface,
					LPD3DENUMTEXTUREFORMATSCALLBACK lpCB,
					LPVOID lpContext)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice2, iface);
    LPDDRAWI_DIRECTDRAW_GBL dd_gbl = This->ddraw_owner->local.lpGbl;
    LPD3DHAL_GLOBALDRIVERDATA data = (LPD3DHAL_GLOBALDRIVERDATA)dd_gbl->lpD3DGlobalDriverData;
    unsigned i;

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

    for (i=0; i<data->dwNumTextureFormats; i++) {
        /* Older D3D apps can't deal with non RGB texture formats,
         * so skip them */
        if (data->lpTextureFormats[i].ddpfPixelFormat.dwFlags & DDPF_RGB)
	    if (!lpCB(&data->lpTextureFormats[i], lpContext))
	        return D3D_OK;
    }

    return D3D_OK;
}

static HRESULT WINAPI
Main_Direct3DDevice2_BeginScene(LPDIRECT3DDEVICE2 iface)
{
    return IDirect3DDevice7_BeginScene(CONVERT7(iface));
}

static HRESULT WINAPI
Main_Direct3DDevice2_EndScene(LPDIRECT3DDEVICE2 iface)
{
    return IDirect3DDevice7_EndScene(CONVERT7(iface));
}

static HRESULT WINAPI
Main_Direct3DDevice2_SetCurrentViewport(LPDIRECT3DDEVICE2 iface,
					LPDIRECT3DVIEWPORT2 lpView)
{
    /* IDirect3DViewportImpl has only one vtable */
    return IDirect3DDevice3_SetCurrentViewport(CONVERT3(iface),
					       (LPDIRECT3DVIEWPORT3)lpView);
}

static HRESULT WINAPI
Main_Direct3DDevice2_SetRenderState(LPDIRECT3DDEVICE2 iface,
				    D3DRENDERSTATETYPE d3dType,
				    DWORD dwState)
{
    return IDirect3DDevice3_SetRenderState(CONVERT3(iface),
					   d3dType, dwState);
}

static HRESULT WINAPI
Main_Direct3DDevice2_GetLightState(LPDIRECT3DDEVICE2 iface,
				   D3DLIGHTSTATETYPE dwLightStateType,
				   LPDWORD lpdwLightState)
{
    return IDirect3DDevice3_GetLightState(CONVERT3(iface),
					  dwLightStateType, lpdwLightState);
}

static HRESULT WINAPI
Main_Direct3DDevice2_SetLightState(LPDIRECT3DDEVICE2 iface,
				   D3DLIGHTSTATETYPE dlsType,
				   DWORD dwState)
{
    return IDirect3DDevice3_SetLightState(CONVERT3(iface),
					  dlsType, dwState);
}

static HRESULT WINAPI
Main_Direct3DDevice2_SetTransform(LPDIRECT3DDEVICE2 iface,
				  D3DTRANSFORMSTATETYPE dtstType,
				  LPD3DMATRIX lpD3DMatrix)
{
    return IDirect3DDevice7_SetTransform(CONVERT7(iface),
					 dtstType, lpD3DMatrix);
}

static DWORD D3D_VT2FVF[] = { 0,
  D3DFVF_VERTEX,
  D3DFVF_LVERTEX,
  D3DFVF_TLVERTEX
};

static HRESULT WINAPI
Main_Direct3DDevice2_DrawPrimitive(LPDIRECT3DDEVICE2 iface,
				   D3DPRIMITIVETYPE d3dptPrimType,
				   D3DVERTEXTYPE d3dvtVertexType,
				   LPVOID lpvVertices,
				   DWORD dwVertexCount,
				   DWORD dwFlags)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice2, iface);
    BOOL in_scene = This->d3dp.in_scene;
    HRESULT hr;
    /* I've encountered a DX5 app that needs this to work outside a scene,
     * so let's just force a scene for it */
    if (!in_scene) IDirect3DDevice7_BeginScene(CONVERT7(iface));
    hr = IDirect3DDevice7_DrawPrimitive(CONVERT7(iface), d3dptPrimType,
					D3D_VT2FVF[d3dvtVertexType],
					lpvVertices, dwVertexCount,
					dwFlags);
    if (!in_scene) IDirect3DDevice7_EndScene(CONVERT7(iface));
    return hr;
}

static HRESULT WINAPI
Main_Direct3DDevice2_DrawIndexedPrimitive(LPDIRECT3DDEVICE2 iface,
					  D3DPRIMITIVETYPE d3dptPrimType,
					  D3DVERTEXTYPE d3dvtVertexType,
					  LPVOID lpvVertices,
					  DWORD dwVertexCount,
					  LPWORD lpwIndices,
					  DWORD dwIndexCount,
					  DWORD dwFlags)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice2, iface);
    BOOL in_scene = This->d3dp.in_scene;
    HRESULT hr;
    /* I've encountered a DX5 app that needs this to work outside a scene,
     * so let's just force a scene for it */
    if (!in_scene) IDirect3DDevice7_BeginScene(CONVERT7(iface));
    hr = IDirect3DDevice7_DrawIndexedPrimitive(CONVERT7(iface), d3dptPrimType,
					       D3D_VT2FVF[d3dvtVertexType],
					       lpvVertices, dwVertexCount,
					       lpwIndices, dwIndexCount,
					       dwFlags);
    if (!in_scene) IDirect3DDevice7_EndScene(CONVERT7(iface));
    return hr;
}


static HRESULT WINAPI
Main_Direct3DDevice2_SetClipStatus(LPDIRECT3DDEVICE2 iface,
				   LPD3DCLIPSTATUS lpD3DClipStatus)
{
    return IDirect3DDevice7_SetClipStatus(CONVERT7(iface), lpD3DClipStatus);
}

static HRESULT WINAPI
Main_Direct3DDevice2_GetClipStatus(LPDIRECT3DDEVICE2 iface,
				   LPD3DCLIPSTATUS lpD3DClipStatus)
{
    return IDirect3DDevice7_GetClipStatus(CONVERT7(iface), lpD3DClipStatus);
}

static HRESULT WINAPI
Main_Direct3DDevice2_SetRenderTarget(LPDIRECT3DDEVICE2 iface,
				     LPDIRECTDRAWSURFACE lpNewRenderTarget,
				     DWORD dwFlags)
{
    return IDirect3DDevice3_SetRenderTarget(CONVERT3(iface),
					    (LPDIRECTDRAWSURFACE4)
					    COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
							       IDirectDrawSurface3,
							       IDirectDrawSurface7,
							       lpNewRenderTarget),
					    dwFlags);
}

static HRESULT WINAPI
Main_Direct3DDevice2_GetRenderTarget(LPDIRECT3DDEVICE2 iface,
				     LPDIRECTDRAWSURFACE *lplpRenderTarget)
{
    LPDIRECTDRAWSURFACE4 lpRenderTarget = NULL;
    HRESULT hr;
    hr = IDirect3DDevice3_GetRenderTarget(CONVERT3(iface),
					  &lpRenderTarget);
    *lplpRenderTarget = (LPDIRECTDRAWSURFACE)
			COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
					   IDirectDrawSurface7,
					   IDirectDrawSurface3,
					   lpRenderTarget);
    return hr;
}

#undef CONVERT3
#undef CONVERT7

static ICOM_VTABLE(IDirect3DDevice2) Main_IDirect3DDevice2_VTable =
{
    Main_Direct3DDevice2_QueryInterface,
    Main_Direct3DDevice2_AddRef,
    Main_Direct3DDevice2_Release,
    Main_Direct3DDevice2_GetCaps,
    (void*)0xdead2001, /* SwapTextureHandles */
    (void*)0xdead2002, /* GetStats */
    Main_Direct3DDevice2_AddViewport,
    Main_Direct3DDevice2_DeleteViewport,
    (void*)0xdead2005, /* NextViewport */
    Main_Direct3DDevice2_EnumTextureFormats,
    Main_Direct3DDevice2_BeginScene,
    Main_Direct3DDevice2_EndScene,
    (void*)0xdead2009, /* GetDirect3D */
    Main_Direct3DDevice2_SetCurrentViewport,
    (void*)0xdead200B, /* GetCurrentViewport */
    Main_Direct3DDevice2_SetRenderTarget,
    Main_Direct3DDevice2_GetRenderTarget,
    (void*)0xdead200E, /* Begin */
    (void*)0xdead200F, /* BeginIndexed */
    (void*)0xdead2010, /* Vertex */
    (void*)0xdead2011, /* Index */
    (void*)0xdead2012, /* End */
    (void*)0xdead2013, /* GetRenderState */
    Main_Direct3DDevice2_SetRenderState,
    Main_Direct3DDevice2_GetLightState,
    Main_Direct3DDevice2_SetLightState,
    Main_Direct3DDevice2_SetTransform,
    (void*)0xdead2018, /* GetTransform */
    (void*)0xdead2019, /* MultiplyTransform */
    Main_Direct3DDevice2_DrawPrimitive,
    Main_Direct3DDevice2_DrawIndexedPrimitive,
    Main_Direct3DDevice2_SetClipStatus,
    Main_Direct3DDevice2_GetClipStatus
};

