/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "Engine/StdH.h"

#include <Engine/Base/Translation.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/Memory.h>
#include <Engine/Base/Console.h>

#include <Engine/Graphics/GfxLibrary.h>
#include <Engine/Graphics/ViewPort.h>

#include <Engine/Templates/StaticStackArray.cpp>
#include <Engine/Templates/DynamicContainer.cpp>
#include <Engine/Templates/Stock_CTextureData.h>

#ifdef SE1_D3D


// asm shortcuts
#define O offset
#define Q qword ptr
#define D dword ptr
#define W  word ptr
#define B  byte ptr

#define ASMOPT 1


extern INDEX gap_iTruformLevel;

extern ULONG _fog_ulTexture;
extern ULONG _haze_ulTexture;


// state variables
extern BOOL GFX_bDepthTest;
extern BOOL GFX_bDepthWrite;
extern BOOL GFX_bAlphaTest;
extern BOOL GFX_bBlending;
extern BOOL GFX_bDithering;
extern BOOL GFX_bClipping;
extern BOOL GFX_bClipPlane;
extern BOOL GFX_bColorArray;
extern BOOL GFX_bFrontFace;
extern BOOL GFX_bTruform;
extern INDEX GFX_iActiveTexUnit;
extern FLOAT GFX_fMinDepthRange;
extern FLOAT GFX_fMaxDepthRange;
extern GfxBlend GFX_eBlendSrc;
extern GfxBlend GFX_eBlendDst;
extern GfxComp  GFX_eDepthFunc;
extern GfxFace  GFX_eCullFace;
extern INDEX GFX_iTexModulation[GFX_MAXTEXUNITS];

extern INDEX GFX_ctVertices;
extern BOOL  D3D_bUseColorArray = FALSE;

// internal vars
static INDEX _iVtxOffset = 0;
static INDEX _iIdxOffset = 0;
static INDEX _iVtxPos = 0;
static INDEX _iTexPass = 0;
static INDEX _iColPass = 0;
static DWORD _dwCurrentVS = NONE;
static ULONG _ulStreamsMask = NONE;
static ULONG _ulLastStreamsMask = NONE;
static BOOL  _bProjectiveMapping = FALSE;
static BOOL  _bLastProjectiveMapping = FALSE;
static DWORD _dwVtxLockFlags;                 // for vertex and normal
static DWORD _dwColLockFlags[GFX_MAXLAYERS];  // for colors
static DWORD _dwTexLockFlags[GFX_MAXLAYERS];  // for texture coords

// shaders created so far
struct VertexShader {
  ULONG vs_ulMask;
  DWORD vs_dwHandle;
};
static CStaticStackArray<VertexShader> _avsShaders;


// device type (HAL is default, REF is only for debuging)
extern const D3DDEVTYPE d3dDevType = D3DDEVTYPE_HAL;

// identity matrix
extern const D3DMATRIX GFX_d3dIdentityMatrix = {
  1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1 };


// sizes of vertex components
#define POSSIZE (4*sizeof(FLOAT))
#define NORSIZE (4*sizeof(FLOAT))
#define TEXSIZE (2*sizeof(FLOAT))
#define TX4SIZE (4*sizeof(FLOAT))
#define COLSIZE (4*sizeof(UBYTE))
#define IDXSIZE (1*sizeof(UWORD))

#define POSIDX (0)
#define COLIDX (1)
#define TEXIDX (2)
#define NORIDX (6)


// SHADER SETUP PARAMS

#define DECLTEXOFS (2*TEXIDX)
#define MAXSTREAMS (7L)

// template shader
DWORD _adwDeclTemplate[] = {
  D3DVSD_STREAM(0),
  D3DVSD_REG( D3DVSDE_POSITION,  D3DVSDT_FLOAT3),
  D3DVSD_STREAM(1),
  D3DVSD_REG( D3DVSDE_DIFFUSE,   D3DVSDT_D3DCOLOR),
  D3DVSD_STREAM(2),
  D3DVSD_REG( D3DVSDE_TEXCOORD0, D3DVSDT_FLOAT2),
  D3DVSD_STREAM(3),
  D3DVSD_REG( D3DVSDE_TEXCOORD1, D3DVSDT_FLOAT2),
  D3DVSD_STREAM(4),
  D3DVSD_REG( D3DVSDE_TEXCOORD2, D3DVSDT_FLOAT2),
  D3DVSD_STREAM(5),
  D3DVSD_REG( D3DVSDE_TEXCOORD3, D3DVSDT_FLOAT2),
  D3DVSD_STREAM(6),
  D3DVSD_REG( D3DVSDE_NORMAL,    D3DVSDT_FLOAT3),
  D3DVSD_END()
};

// current shader
DWORD _adwCurrentDecl[2*MAXSTREAMS+1];



// check whether texture format is supported in D3D
static BOOL HasTextureFormat_D3D( D3DFORMAT d3dTextureFormat)
{
  // quickie?
  const D3DFORMAT d3dScreenFormat = _pGfx->gl_d3dColorFormat;
  if( d3dTextureFormat==D3DFMT_UNKNOWN || d3dScreenFormat==NONE) return TRUE;
  // check
  extern const D3DDEVTYPE d3dDevType;
  HRESULT hr = _pGfx->gl_pD3D->CheckDeviceFormat( _pGfx->gl_iCurrentAdapter, d3dDevType, d3dScreenFormat,
                                                  0, D3DRTYPE_TEXTURE, d3dTextureFormat);
  return( hr==D3D_OK);
}


#define VTXSIZE (POSSIZE + (_pGfx->gl_iMaxTessellationLevel>0 ? NORSIZE : 0) + TX4SIZE \
               + COLSIZE *  _pGfx->gl_ctColBuffers \
               + TEXSIZE * (_pGfx->gl_ctTexBuffers-1))


// returns number of vertices based on vertex size and required size in memory (KB)
extern INDEX VerticesFromSize_D3D( SLONG &slSize)
{
  slSize = Clamp( slSize, 64L, 4096L);
  return (slSize*1024 / VTXSIZE);
}

// returns size in memory based on number of vertices
extern SLONG SizeFromVertices_D3D( INDEX ctVertices)
{
  return (ctVertices * VTXSIZE);
}
   

// construct vertex shader out of streams' bit-mask
extern DWORD SetupShader_D3D( ULONG ulStreamsMask)
{
  HRESULT hr;
  const LPDIRECT3DDEVICE8 pd3dDev = _pGfx->gl_pd3dDevice;
  const INDEX ctShaders = _avsShaders.Count();
  INDEX iVS;

  // delete all shaders?
  if( ulStreamsMask==NONE) {
    for( iVS=0; iVS<ctShaders; iVS++) {
      hr = pd3dDev->DeleteVertexShader(_avsShaders[iVS].vs_dwHandle);
      D3D_CHECKERROR(hr);
    } // free array
    _avsShaders.PopAll();
    _dwCurrentVS = NONE;
    return NONE;
  }

  // see if required shader has already been created
  for( iVS=0; iVS<ctShaders; iVS++) {
    if( _avsShaders[iVS].vs_ulMask==ulStreamsMask) return _avsShaders[iVS].vs_dwHandle;
  }

  // darn, need to create shader :(
  VertexShader &vs = _avsShaders.Push();
  vs.vs_ulMask = ulStreamsMask;

  // pre-adjustment for eventual projective mapping
  _adwDeclTemplate[DECLTEXOFS+1] = D3DVSD_REG( D3DVSDE_TEXCOORD0, (ulStreamsMask&0x1000) ? D3DVSDT_FLOAT4 : D3DVSDT_FLOAT2);
  ulStreamsMask &= ~0x1000;

  // process mask, bit by bit
  INDEX iSrcDecl=0, iDstDecl=0;
  while( iSrcDecl<MAXSTREAMS)
  { // add declarator if used
    if( ulStreamsMask&1) {
      _adwCurrentDecl[iDstDecl*2+0] = _adwDeclTemplate[iSrcDecl*2+0];
      _adwCurrentDecl[iDstDecl*2+1] = _adwDeclTemplate[iSrcDecl*2+1];
      iDstDecl++;
    } // move to next declarator
    iSrcDecl++;
    ulStreamsMask >>= 1;
  }
  // mark end
  _adwCurrentDecl[iDstDecl*2] = D3DVSD_END();
  ASSERT( iDstDecl < MAXSTREAMS);
  ASSERT( _iTexPass>=0 && _iColPass>=0);
  ASSERT( _iVtxPos >=0 && _iVtxPos<65536);

  // create new vertex shader
  const DWORD dwFlags = (_pGfx->gl_ulFlags&GLF_D3D_USINGHWTNL) ? NONE : D3DUSAGE_SOFTWAREPROCESSING;
  hr = pd3dDev->CreateVertexShader( &_adwCurrentDecl[0], NULL, &vs.vs_dwHandle, dwFlags);
  D3D_CHECKERROR(hr);

  // store and reset shader
  _pGfx->gl_dwVertexShader = 0;
  return vs.vs_dwHandle;
}



// prepare vertex arrays for drawing
extern void SetupVertexArrays_D3D( INDEX ctVertices)
{
  INDEX i;
  HRESULT hr;
  ASSERT( ctVertices>=0);

  // do nothing if buffer is sufficient
  ctVertices = ClampUp( ctVertices, 65535L); // need to clamp max vertices first
  if( ctVertices!=0 && ctVertices<=_pGfx->gl_ctVertices) return;

  // determine SW or HW VP and NPatches usage (Truform) 
  DWORD dwFlags = D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY;
  const BOOL bNPatches = (_pGfx->gl_iMaxTessellationLevel>0 && gap_iTruformLevel>0);
  if( !(_pGfx->gl_ulFlags&GLF_D3D_USINGHWTNL)) dwFlags |= D3DUSAGE_SOFTWAREPROCESSING;
  if( bNPatches) dwFlags |= D3DUSAGE_NPATCHES;
  const LPDIRECT3DDEVICE8 pd3dDev = _pGfx->gl_pd3dDevice;

  // deallocate if needed
  if( _pGfx->gl_pd3dVtx!=NULL)
  {
    // vertex and eventual normal array
    D3DRELEASE( _pGfx->gl_pd3dVtx, TRUE);
    if( _pGfx->gl_pd3dNor!=NULL) D3DRELEASE( _pGfx->gl_pd3dNor, TRUE);
    // color arrays
    for( i=0; i<_pGfx->gl_ctColBuffers; i++) {
      ASSERT( _pGfx->gl_pd3dCol[i]!=NULL);
      D3DRELEASE( _pGfx->gl_pd3dCol[i], TRUE);
    }
    // texcoord arrays
    for( i=0; i<_pGfx->gl_ctTexBuffers; i++) {
      ASSERT( _pGfx->gl_pd3dTex[i]!=NULL);
      D3DRELEASE( _pGfx->gl_pd3dTex[i], TRUE);
    }
    // reset all streams, too
    for( i=0; i<_pGfx->gl_ctMaxStreams; i++) {
      hr = pd3dDev->SetStreamSource( i, NULL,0);
      D3D_CHECKERROR(hr);
    }
  }

  // allocate if needed
  if( ctVertices>0)
  {
    // update max vertex count
    if( _pGfx->gl_ctVertices < ctVertices) _pGfx->gl_ctVertices = ctVertices;
    else ctVertices = _pGfx->gl_ctVertices;
    // create buffers
    hr = pd3dDev->CreateVertexBuffer( ctVertices*POSSIZE, dwFlags, 0, D3DPOOL_DEFAULT, &_pGfx->gl_pd3dVtx);
    D3D_CHECKERROR(hr);
    // normals buffer only if required
    if( bNPatches) { 
      hr = pd3dDev->CreateVertexBuffer( ctVertices*NORSIZE, dwFlags, 0, D3DPOOL_DEFAULT, &_pGfx->gl_pd3dNor);
      D3D_CHECKERROR(hr);
    }
    // all color buffers
    for( i=0; i<_pGfx->gl_ctColBuffers; i++) {
      hr = pd3dDev->CreateVertexBuffer( ctVertices*COLSIZE, dwFlags, 0, D3DPOOL_DEFAULT, &_pGfx->gl_pd3dCol[i]);
      D3D_CHECKERROR(hr);
    }
    // 1st texture buffer might have projective mapping
    hr = pd3dDev->CreateVertexBuffer( ctVertices*TX4SIZE, dwFlags, 0, D3DPOOL_DEFAULT, &_pGfx->gl_pd3dTex[0]);
    D3D_CHECKERROR(hr);
    for( i=1; i<_pGfx->gl_ctTexBuffers; i++) {
      hr = pd3dDev->CreateVertexBuffer( ctVertices*TEXSIZE, dwFlags, 0, D3DPOOL_DEFAULT, &_pGfx->gl_pd3dTex[i]);
      D3D_CHECKERROR(hr);
    }
  }
  // just switch it off if not needed any more (i.e. D3D is shutting down)
  else _pGfx->gl_ctVertices = 0;

  // reset and check
  _iVtxOffset = 0;
  _pGfx->gl_dwVertexShader = NONE;
  _ulStreamsMask = NONE;
  _ulLastStreamsMask = NONE;
  _bProjectiveMapping = FALSE;
  _bLastProjectiveMapping = FALSE;
  // reset locking flags
  _dwVtxLockFlags = D3DLOCK_DISCARD;
  for( i=0; i<GFX_MAXLAYERS; i++) _dwColLockFlags[i] = _dwTexLockFlags[i] = D3DLOCK_DISCARD;
  ASSERT(_pGfx->gl_ctVertices<65536);
}



// prepare index arrays for drawing
extern void SetupIndexArray_D3D( INDEX ctIndices)
{
  HRESULT hr;
  ASSERT( ctIndices>=0);
  // clamp max indices
  ctIndices = ClampUp( ctIndices, 65535L);

  // do nothing if buffer is sufficient
  if( ctIndices!=0 && ctIndices<=_pGfx->gl_ctIndices) return;

  // determine SW or HW VP
  DWORD dwFlags = D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY;
  if( !(_pGfx->gl_ulFlags&GLF_D3D_USINGHWTNL)) dwFlags |= D3DUSAGE_SOFTWAREPROCESSING;
  if( _pGfx->gl_iMaxTessellationLevel>0 && gap_iTruformLevel>0) dwFlags |= D3DUSAGE_NPATCHES;
  const LPDIRECT3DDEVICE8 pd3dDev = _pGfx->gl_pd3dDevice;
  
  // dealocate if needed
  if( _pGfx->gl_ctIndices>0) D3DRELEASE( _pGfx->gl_pd3dIdx, TRUE);

  // allocate if needed
  if( ctIndices>0)
  {
    // eventually update max index count
    if( _pGfx->gl_ctIndices < ctIndices) _pGfx->gl_ctIndices = ctIndices;
    else ctIndices = _pGfx->gl_ctIndices;
    // create buffer
    hr = pd3dDev->CreateIndexBuffer( ctIndices*IDXSIZE, dwFlags, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &_pGfx->gl_pd3dIdx);
    D3D_CHECKERROR(hr);
    // set it
    hr = _pGfx->gl_pd3dDevice->SetIndices( _pGfx->gl_pd3dIdx, 0);
    D3D_CHECKERROR(hr);
  }
  // just switch it off if not needed any more (i.e. D3D is shutting down)
  else _pGfx->gl_ctIndices = 0;
  
  // reset and check
  _iIdxOffset = 0;
  ASSERT(_pGfx->gl_ctIndices<65536);
}



// initialize Direct3D driver
BOOL CGfxLibrary::InitDriver_D3D(void)
{
  // check for presence of DirectX 8
  gl_hiDriver = LoadLibraryA( "D3D8.DLL");
  if( gl_hiDriver==NONE) {
    // not present - BUAHHAHAHAHAR :)
    CPrintF( "DX8 error: API not installed.\n");
    gl_gaAPI[GAT_D3D].ga_ctAdapters = 0;
    return FALSE; 
  }

  // query DX8 interface
  IDirect3D8* (WINAPI *pDirect3DCreate8)(UINT SDKVersion);
  pDirect3DCreate8 = (IDirect3D8* (WINAPI *)(UINT SDKVersion))GetProcAddress( gl_hiDriver, "Direct3DCreate8");
  if( pDirect3DCreate8==NULL) {
    // cannot init
    CPrintF( "DX8 error: Cannot get entry procedure address.\n");
    FreeLibrary(gl_hiDriver);
    gl_hiDriver = NONE;
    return FALSE; 
  }

  // init DX8
  gl_pD3D = pDirect3DCreate8(D3D_SDK_VERSION);
  if( gl_pD3D==NULL) {
    // cannot start
    CPrintF( "DX8 error: Cannot be initialized.\n");
    FreeLibrary(gl_hiDriver);
    gl_hiDriver = NONE;
    return FALSE;
  }
  // made it!
  return TRUE;
}


// initialize Direct3D driver
void CGfxLibrary::EndDriver_D3D(void)
{
  // unbind textures
  if( _pTextureStock!=NULL) {
    {FOREACHINDYNAMICCONTAINER( _pTextureStock->st_ctObjects, CTextureData, ittd) {
      CTextureData &td = *ittd;
      td.td_tpLocal.Clear();
      td.Unbind();
    }}
  }
  // unbind fog, haze and flat texture
  gfxDeleteTexture( _fog_ulTexture); 
  gfxDeleteTexture( _haze_ulTexture);
  ASSERT( _ptdFlat!=NULL);
  _ptdFlat->td_tpLocal.Clear();
  _ptdFlat->Unbind();

  // reset shader and vertices
  SetupShader_D3D(NONE); 
  SetupVertexArrays_D3D(0); 
  SetupIndexArray_D3D(0); 
  gl_d3dColorFormat = (D3DFORMAT)NONE;
  gl_d3dDepthFormat = (D3DFORMAT)NONE;

  // shutdown device and d3d
  INDEX iRef;
  iRef = gl_pd3dDevice->Release();
  iRef = gl_pD3D->Release();
}


// prepare current viewport for rendering thru Direct3D
BOOL CGfxLibrary::SetCurrentViewport_D3D(CViewPort *pvp)
{
  // determine full screen mode
  CDisplayMode dm;
  RECT rectWindow;
  _pGfx->GetCurrentDisplayMode(dm);
  ASSERT( (dm.dm_pixSizeI==0 && dm.dm_pixSizeJ==0) || (dm.dm_pixSizeI!=0 && dm.dm_pixSizeJ!=0));
	GetClientRect( pvp->vp_hWnd, &rectWindow);
	const PIX pixWinSizeI = rectWindow.right  - rectWindow.left;
	const PIX pixWinSizeJ = rectWindow.bottom - rectWindow.top;

  // full screen allows only one window (main one, which has already been initialized)
  if( dm.dm_pixSizeI==pixWinSizeI && dm.dm_pixSizeJ==pixWinSizeJ) {
    gl_pvpActive = pvp;  // remember as current viewport (must do that BEFORE InitContext)
    if( gl_ulFlags & GLF_INITONNEXTWINDOW) InitContext_D3D();
    gl_ulFlags &= ~GLF_INITONNEXTWINDOW;
    return TRUE; 
  }

  // if must init entire D3D
  if( gl_ulFlags & GLF_INITONNEXTWINDOW) {
    gl_ulFlags &= ~GLF_INITONNEXTWINDOW;
    // reopen window
    pvp->CloseCanvas();
    pvp->OpenCanvas();
    gl_pvpActive = pvp;
    InitContext_D3D();
    pvp->vp_ctDisplayChanges = gl_ctDriverChanges;
    return TRUE;
  }

  // if window was not set for this driver
  if( pvp->vp_ctDisplayChanges<gl_ctDriverChanges) {
    // reopen window
    pvp->CloseCanvas();
    pvp->OpenCanvas();
    pvp->vp_ctDisplayChanges = gl_ctDriverChanges;
    gl_pvpActive = pvp;
    return TRUE;
  }

  // no need to set context if it is the same window as last time
  if( gl_pvpActive!=NULL && gl_pvpActive->vp_hWnd==pvp->vp_hWnd) return TRUE;

  // set rendering target
  HRESULT hr;
  LPDIRECT3DSURFACE8 pColorSurface;
  hr = pvp->vp_pSwapChain->GetBackBuffer( 0, D3DBACKBUFFER_TYPE_MONO, &pColorSurface);
  if( hr!=D3D_OK) return FALSE;
  hr = gl_pd3dDevice->SetRenderTarget( pColorSurface, pvp->vp_pSurfDepth);
  D3DRELEASE( pColorSurface, TRUE);
  if( hr!=D3D_OK) return FALSE;

  // remember as current window
  gl_pvpActive = pvp;
  return TRUE;
}



// prepares Direct3D drawing context
void CGfxLibrary::InitContext_D3D()
{
  // must have context
  ASSERT( gl_pvpActive!=NULL);

  // report header
  CPrintF( TRANS("\n* Direct3D context created: *----------------------------------\n"));
  CDisplayAdapter &da = gl_gaAPI[GAT_D3D].ga_adaAdapter[gl_iCurrentAdapter];
  CPrintF( "  (%s, %s, %s)\n\n", da.da_strVendor, da.da_strRenderer, da.da_strVersion);
  HRESULT hr;

  // reset engine's internal Direct3D state variables
  GFX_bTruform   = FALSE;
  GFX_bClipping  = TRUE;
  GFX_bFrontFace = TRUE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE);     D3D_CHECKERROR(hr);  GFX_bDepthTest  = FALSE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE);      D3D_CHECKERROR(hr);  GFX_bDepthWrite = FALSE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE);   D3D_CHECKERROR(hr);  GFX_bAlphaTest  = FALSE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE);  D3D_CHECKERROR(hr);  GFX_bBlending   = FALSE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_DITHERENABLE, TRUE);       D3D_CHECKERROR(hr);  GFX_bDithering  = TRUE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_CLIPPLANEENABLE, FALSE);   D3D_CHECKERROR(hr);  GFX_bClipPlane  = FALSE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE);   D3D_CHECKERROR(hr);  GFX_eCullFace   = GFX_NONE;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL);  D3D_CHECKERROR(hr);  GFX_eDepthFunc  = GFX_LESS_EQUAL;
  hr = gl_pd3dDevice->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_ONE);  D3D_CHECKERROR(hr);  GFX_eBlendSrc   = GFX_ONE; 
  hr = gl_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE);  D3D_CHECKERROR(hr);  GFX_eBlendDst   = GFX_ONE;
    
  // (re)set some D3D defaults
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); D3D_CHECKERROR(hr); 
  hr = gl_pd3dDevice->SetRenderState( D3DRS_ALPHAREF,  128);                 D3D_CHECKERROR(hr); 

  // constant color default setup
  hr = gl_pd3dDevice->SetRenderState( D3DRS_COLORVERTEX, FALSE);      D3D_CHECKERROR(hr); 
  hr = gl_pd3dDevice->SetRenderState( D3DRS_LIGHTING,    TRUE);       D3D_CHECKERROR(hr); 
  hr = gl_pd3dDevice->SetRenderState( D3DRS_AMBIENT,     0xFFFFFFFF); D3D_CHECKERROR(hr); 
  D3DMATERIAL8 d3dMaterial;
  memset( &d3dMaterial, 0, sizeof(d3dMaterial));
  d3dMaterial.Diffuse.r = d3dMaterial.Ambient.r = 1.0f;
  d3dMaterial.Diffuse.g = d3dMaterial.Ambient.g = 1.0f;
  d3dMaterial.Diffuse.b = d3dMaterial.Ambient.b = 1.0f;
  d3dMaterial.Diffuse.a = d3dMaterial.Ambient.a = 1.0f;
  hr = gl_pd3dDevice->SetMaterial(&d3dMaterial);
  D3D_CHECKERROR(hr); 
  GFX_bColorArray = FALSE;

  // disable texturing
  extern BOOL GFX_abTexture[GFX_MAXTEXUNITS];
  for( INDEX iUnit=0; iUnit<GFX_MAXTEXUNITS; iUnit++) {
    GFX_abTexture[iUnit] = FALSE;
    GFX_iTexModulation[iUnit] = 1;
    hr = gl_pd3dDevice->SetTexture( iUnit, NULL);                                      D3D_CHECKERROR(hr);
    hr = gl_pd3dDevice->SetTextureStageState( iUnit, D3DTSS_COLOROP, D3DTOP_DISABLE);  D3D_CHECKERROR(hr); 
    hr = gl_pd3dDevice->SetTextureStageState( iUnit, D3DTSS_ALPHAOP, D3DTOP_MODULATE); D3D_CHECKERROR(hr); 
  }
  // set default texture unit and modulation mode
  GFX_iActiveTexUnit = 0;
  // reset frustum/ortho matrix
  extern BOOL  GFX_bViewMatrix;
  extern FLOAT GFX_fLastL, GFX_fLastR, GFX_fLastT, GFX_fLastB, GFX_fLastN, GFX_fLastF;
  GFX_fLastL = GFX_fLastR = GFX_fLastT = GFX_fLastB = GFX_fLastN = GFX_fLastF = 0;
  GFX_bViewMatrix = TRUE;

  // reset depth range
  D3DVIEWPORT8 d3dViewPort = { 0,0, 8,8, 0,1 };
  hr = gl_pd3dDevice->SetViewport( &d3dViewPort);  D3D_CHECKERROR(hr);
  hr = gl_pd3dDevice->GetViewport( &d3dViewPort);  D3D_CHECKERROR(hr);
  ASSERT( d3dViewPort.MinZ==0 && d3dViewPort.MaxZ==1);
  GFX_fMinDepthRange = 0.0f;
  GFX_fMaxDepthRange = 1.0f;

  // get capabilites
  D3DCAPS8 d3dCaps;
  hr = gl_pd3dDevice->GetDeviceCaps( &d3dCaps);
  D3D_CHECKERROR(hr);

  // determine rasterizer acceleration
  gl_ulFlags &= ~GLF_HASACCELERATION;
  if( (d3dCaps.DevCaps & D3DDEVCAPS_HWRASTERIZATION)
    || d3dDevType==D3DDEVTYPE_REF) gl_ulFlags |= GLF_HASACCELERATION;

  // determine support for 32-bit textures
  gl_ulFlags &= ~GLF_32BITTEXTURES;
  if( HasTextureFormat_D3D(D3DFMT_X8R8G8B8)
   || HasTextureFormat_D3D(D3DFMT_A8R8G8B8)) gl_ulFlags |= GLF_32BITTEXTURES;

  // determine support for compressed textures
  gl_ulFlags &= ~GLF_TEXTURECOMPRESSION;
  if( HasTextureFormat_D3D(D3DFMT_DXT1)) gl_ulFlags |= GLF_TEXTURECOMPRESSION;

  // determine max supported dimension of texture
  gl_pixMaxTextureDimension = d3dCaps.MaxTextureWidth;
  ASSERT( gl_pixMaxTextureDimension == d3dCaps.MaxTextureHeight); // perhaps not ?

  // determine support for disabling of color buffer writes
  gl_ulFlags &= ~GLF_D3D_COLORWRITES;
  if( d3dCaps.PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) gl_ulFlags |= GLF_D3D_COLORWRITES;

  // determine support for custom clip planes
  gl_ulFlags &= ~GLF_D3D_CLIPPLANE;
  if( d3dCaps.MaxUserClipPlanes>0) gl_ulFlags |= GLF_D3D_CLIPPLANE;
  else CPrintF( TRANS("User clip plane not supported - mirrors will not work well.\n"));

  // determine support for texture LOD biasing
  gl_fMaxTextureLODBias = 0.0f;
  if( d3dCaps.RasterCaps & D3DPRASTERCAPS_MIPMAPLODBIAS) {
    gl_fMaxTextureLODBias = 4.0f;
  }

  // determine support for anisotropic filtering
  gl_iMaxTextureAnisotropy = 1;
  if( d3dCaps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) {
    gl_iMaxTextureAnisotropy = d3dCaps.MaxAnisotropy;
    ASSERT( gl_iMaxTextureAnisotropy>1); 
  }

  // determine support for z-biasing
  gl_ulFlags &= ~GLF_D3D_ZBIAS;
  if( d3dCaps.RasterCaps & D3DPRASTERCAPS_ZBIAS) gl_ulFlags |= GLF_D3D_ZBIAS;

  // check support for vsync swapping
  gl_ulFlags &= ~GLF_VSYNC;
  if( d3dCaps.PresentationIntervals & D3DPRESENT_INTERVAL_IMMEDIATE) {
    if( d3dCaps.PresentationIntervals & D3DPRESENT_INTERVAL_ONE) gl_ulFlags |= GLF_VSYNC;  
  } else CPrintF( TRANS("  Vertical syncronization cannot be disabled.\n"));

  // determine support for N-Patches
  extern INDEX truform_iLevel;
  extern BOOL  truform_bLinear;
  truform_iLevel  = -1;
  truform_bLinear = FALSE;
  gl_iTessellationLevel    = 0;
  gl_iMaxTessellationLevel = 0;
  INDEX ctMinStreams = GFX_MINSTREAMS; // set minimum number of required streams
  if( d3dCaps.DevCaps & D3DDEVCAPS_NPATCHES) {
    if( gl_ctMaxStreams>GFX_MINSTREAMS) {
      gl_iMaxTessellationLevel = 7;
      hr = gl_pd3dDevice->SetRenderState( D3DRS_PATCHEDGESTYLE, D3DPATCHEDGE_DISCRETE);
      D3D_CHECKERROR(hr);
      ctMinStreams++; // need an extra stream for normals now
    } else CPrintF( TRANS("Not enough streams - N-Patches cannot be used.\n"));
  }

  // determine support for multi-texturing (only if Modulate2X mode is supported!)
  gl_ctTextureUnits = 1;
  gl_ctRealTextureUnits = d3dCaps.MaxSimultaneousTextures;
  if( gl_ctRealTextureUnits>1) {
    // check everything that is required for multi-texturing
    if( !(d3dCaps.TextureOpCaps&D3DTOP_MODULATE2X)) CPrintF( TRANS("Texture operation MODULATE2X missing - multi-texturing cannot be used.\n"));
    else if( gl_ctMaxStreams<=ctMinStreams)         CPrintF( TRANS("Not enough streams - multi-texturing cannot be used.\n"));
    else gl_ctTextureUnits = Min( GFX_MAXTEXUNITS, Min( gl_ctRealTextureUnits, 1+gl_ctMaxStreams-ctMinStreams));
  }

  // setup fog and haze textures
  extern PIX _fog_pixSizeH;
  extern PIX _fog_pixSizeL;
  extern PIX _haze_pixSize;
  _fog_ulTexture  = NONE;
  _haze_ulTexture = NONE;
  _fog_pixSizeH = 0;
  _fog_pixSizeL = 0;
  _haze_pixSize = 0;

  // prepare pattern texture
  extern CTexParams _tpPattern;
  extern ULONG _ulPatternTexture;
  extern ULONG _ulLastUploadedPattern;
  _ulPatternTexture = NONE;
  _ulLastUploadedPattern = 0;
  _tpPattern.Clear();

  // determine number of color/texcoord buffers
  gl_ctTexBuffers = gl_ctTextureUnits;
  gl_ctColBuffers = 1;
  INDEX ctStreamsRemain = gl_ctMaxStreams - (ctMinStreams-1+gl_ctTextureUnits); // -1 because of 1 texture unit inside MinStreams
  FOREVER {
    // done if no more or enough streams
    if( ctStreamsRemain==0 || (gl_ctTexBuffers==GFX_MAXLAYERS && gl_ctColBuffers==GFX_MAXLAYERS)) break;
    // increase number of tex or color buffers
    if( gl_ctColBuffers<gl_ctTexBuffers) gl_ctColBuffers++;
    else gl_ctTexBuffers++;
    // next stream (if available)
    ctStreamsRemain--;
  }

  // prepare vertex arrays
  gl_pd3dIdx = NULL;
  gl_pd3dVtx = NULL;
  gl_pd3dNor = NULL;
  for( INDEX i=0; i<GFX_MAXLAYERS; i++) gl_pd3dCol[i] = gl_pd3dTex[i] = NULL;
  ASSERT( gl_ctTexBuffers>0 && gl_ctTexBuffers<=GFX_MAXLAYERS);
  ASSERT( gl_ctColBuffers>0 && gl_ctColBuffers<=GFX_MAXLAYERS);
  gl_ctVertices = 0;
  gl_ctIndices  = 0;
  extern INDEX d3d_iVertexBuffersSize;
  extern INDEX _iLastVertexBufferSize;
  const  INDEX ctVertices = VerticesFromSize_D3D(d3d_iVertexBuffersSize);
  _iLastVertexBufferSize  = d3d_iVertexBuffersSize;
  SetupVertexArrays_D3D(ctVertices); 
  SetupIndexArray_D3D(2*ctVertices);

  // reset texture filtering and some static vars
  _tpGlobal[0].Clear();
  _tpGlobal[1].Clear();
  _tpGlobal[2].Clear();
  _tpGlobal[3].Clear();
  _avsShaders.Clear();
  _iVtxOffset = 0;
  _iIdxOffset = 0;
  _dwCurrentVS = NONE;
  _ulStreamsMask = NONE;
  _ulLastStreamsMask = NONE;
  _bProjectiveMapping = FALSE;
  _bLastProjectiveMapping = FALSE;
  gl_dwVertexShader = NONE;
  GFX_ctVertices = 0;
  // reset locking flags
  _dwVtxLockFlags = D3DLOCK_DISCARD;
  for( i=0; i<GFX_MAXLAYERS; i++) _dwColLockFlags[i] = _dwTexLockFlags[i] = D3DLOCK_DISCARD;

  // set default texture filtering/biasing
  extern INDEX gap_iTextureFiltering;
  extern INDEX gap_iTextureAnisotropy;
  extern FLOAT gap_fTextureLODBias;
  gfxSetTextureFiltering( gap_iTextureFiltering, gap_iTextureAnisotropy);
  gfxSetTextureBiasing( gap_fTextureLODBias);

  // mark pretouching and probing
  extern BOOL _bNeedPretouch;
  _bNeedPretouch = TRUE;
  gl_bAllowProbing = FALSE;

  // update console system vars
  extern void UpdateGfxSysCVars(void);
  UpdateGfxSysCVars();

  // reload all loaded textures and eventually shadowmaps
  extern INDEX shd_bCacheAll;
  extern void ReloadTextures(void);
  extern void CacheShadows(void);
  ReloadTextures();
  if( shd_bCacheAll) CacheShadows();
}



// find depth buffer format (for specified color format) that closest matches required bit depth
static D3DFORMAT FindDepthFormat_D3D( INDEX iAdapter, D3DFORMAT d3dfColor, INDEX &iDepthBits)
{
  // safeties
  ASSERT( iDepthBits==0 || iDepthBits==16 || iDepthBits==24 || iDepthBits==32);
  ASSERT( d3dfColor==D3DFMT_R5G6B5 || d3dfColor==D3DFMT_X8R8G8B8);

  // adjust required Z-depth from color depth if needed
       if( iDepthBits==0 && d3dfColor==D3DFMT_R5G6B5)   iDepthBits = 16;
  else if( iDepthBits==0 && d3dfColor==D3DFMT_X8R8G8B8) iDepthBits = 32;

  // determine closest z-depth
  D3DFORMAT ad3dFormats[] = { D3DFMT_D32,   // 32-bits
                              D3DFMT_D24X8, D3DFMT_D24S8, D3DFMT_D24X4S4, // 24-bits
                              D3DFMT_D16,   D3DFMT_D15S1, D3DFMT_D16_LOCKABLE }; // 16-bits
  const INDEX ctFormats = sizeof(ad3dFormats) / sizeof(ad3dFormats[0]);
  
  // find starting point from which format to search for support
  INDEX iStart;
       if( iDepthBits==32) iStart = 0;
  else if( iDepthBits==24) iStart = 1;
  else                     iStart = 4;
  // search
  INDEX i;
  HRESULT hr;
  for( i=iStart; i<ctFormats; i++) {
    hr = _pGfx->gl_pD3D->CheckDepthStencilMatch( iAdapter, d3dDevType, d3dfColor, d3dfColor, ad3dFormats[i]);
    if( hr==D3D_OK) break;
  } 

  // not found?
  if( i==ctFormats) {
    // do additional check for whole format list
    for( i=0; i<iStart; i++) {
      hr = _pGfx->gl_pD3D->CheckDepthStencilMatch( iAdapter, d3dDevType, d3dfColor, d3dfColor, ad3dFormats[i]); 
      if( hr==D3D_OK) break;
    }
    // what, z-buffer still not supported?
    if( i==iStart) {
      ASSERT( "FindDepthFormat_D3D: Z-buffer format not found?!" );
      iDepthBits = 0;
      return D3DFMT_UNKNOWN; 
    }
  } 
  // aaaah, found! :)
  ASSERT( i>=0 && i<ctFormats);
       if( i>3) iDepthBits = 16;
  else if( i>0) iDepthBits = 24;
  else          iDepthBits = 32;
  return ad3dFormats[i];
}


// prepare display mode
BOOL CGfxLibrary::InitDisplay_D3D( INDEX iAdapter, PIX pixSizeI, PIX pixSizeJ,
                                   enum DisplayDepth eColorDepth)
{
  // reset
  HRESULT hr;
  D3DDISPLAYMODE d3dDisplayMode;
  D3DPRESENT_PARAMETERS d3dPresentParams;
  gl_pD3D->GetAdapterDisplayMode( iAdapter, &d3dDisplayMode);
  memset( &d3dPresentParams, 0, sizeof(d3dPresentParams));

  // readout device capabilites
  D3DCAPS8 d3dCaps;
  hr = gl_pD3D->GetDeviceCaps( iAdapter, d3dDevType, &d3dCaps);
  D3D_CHECKERROR(hr);

  // clamp depth/stencil values
  extern INDEX gap_iDepthBits;
  extern INDEX gap_iStencilBits;
       if( gap_iDepthBits <12) gap_iDepthBits   = 0;
  else if( gap_iDepthBits <22) gap_iDepthBits   = 16;
  else if( gap_iDepthBits <28) gap_iDepthBits   = 24;
  else                         gap_iDepthBits   = 32;
       if( gap_iStencilBits<3) gap_iStencilBits = 0;
  else if( gap_iStencilBits<7) gap_iStencilBits = 4;
  else                         gap_iStencilBits = 8;

  // prepare  
  INDEX iZDepth = gap_iDepthBits;
  D3DFORMAT d3dDepthFormat = D3DFMT_UNKNOWN;
  D3DFORMAT d3dColorFormat = d3dDisplayMode.Format;
  d3dPresentParams.BackBufferCount = 1;
  d3dPresentParams.MultiSampleType = D3DMULTISAMPLE_NONE; // !!!! TODO
  d3dPresentParams.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
  d3dPresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
  const BOOL bFullScreen = (pixSizeI>0 && pixSizeJ>0); 

  // setup for full screen
  if( bFullScreen) {
    // determine color and depth format
    if( eColorDepth==DD_16BIT) d3dColorFormat = D3DFMT_R5G6B5;
    if( eColorDepth==DD_32BIT) d3dColorFormat = D3DFMT_X8R8G8B8;
    d3dDepthFormat = FindDepthFormat_D3D( iAdapter, d3dColorFormat, iZDepth);
    // determine refresh rate and presentation interval
    extern INDEX gap_iRefreshRate;
    const UINT uiRefresh = gap_iRefreshRate>0 ? gap_iRefreshRate : D3DPRESENT_RATE_DEFAULT;
    const SLONG slIntervals = d3dCaps.PresentationIntervals;
    UINT uiInterval = (slIntervals&D3DPRESENT_INTERVAL_IMMEDIATE) ? D3DPRESENT_INTERVAL_IMMEDIATE : D3DPRESENT_INTERVAL_ONE;
    extern INDEX gap_iSwapInterval;
    switch(gap_iSwapInterval) {
    case 1:  if( slIntervals&D3DPRESENT_INTERVAL_ONE)   uiInterval=D3DPRESENT_INTERVAL_ONE;    break; 
    case 2:  if( slIntervals&D3DPRESENT_INTERVAL_TWO)   uiInterval=D3DPRESENT_INTERVAL_TWO;    break; 
    case 3:  if( slIntervals&D3DPRESENT_INTERVAL_THREE) uiInterval=D3DPRESENT_INTERVAL_THREE;  break; 
    case 4:  if( slIntervals&D3DPRESENT_INTERVAL_FOUR)  uiInterval=D3DPRESENT_INTERVAL_FOUR;   break; 
    default: break;
    } // construct back cvar
    switch(uiInterval) {
    case 1:  gap_iSwapInterval=1;  break; 
    case 2:  gap_iSwapInterval=2;  break; 
    case 3:  gap_iSwapInterval=3;  break; 
    case 4:  gap_iSwapInterval=4;  break; 
    default: gap_iSwapInterval=0;  break;
    } gl_iSwapInterval = gap_iSwapInterval; // copy to gfx lib
    // set context directly to main window
    d3dPresentParams.Windowed = FALSE;
    d3dPresentParams.BackBufferWidth  = pixSizeI;
    d3dPresentParams.BackBufferHeight = pixSizeJ;
    d3dPresentParams.BackBufferFormat = d3dColorFormat;
    d3dPresentParams.EnableAutoDepthStencil = TRUE;
    d3dPresentParams.AutoDepthStencilFormat = d3dDepthFormat;
    d3dPresentParams.FullScreen_RefreshRateInHz = uiRefresh;
    d3dPresentParams.FullScreen_PresentationInterval = uiInterval;
  }
  // setup for windowed mode
  else {
    // create dummy Direct3D context
    d3dPresentParams.Windowed = TRUE;
    d3dPresentParams.BackBufferWidth  = 8;
    d3dPresentParams.BackBufferHeight = 8;
    d3dPresentParams.BackBufferFormat = d3dColorFormat;
    d3dPresentParams.EnableAutoDepthStencil = FALSE;
    d3dDepthFormat = FindDepthFormat_D3D( iAdapter, d3dColorFormat, iZDepth);
    gl_iSwapInterval = -1;
  }
  // determine HW or SW vertex processing
  DWORD dwVP = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
  gl_ulFlags &= ~(GLF_D3D_HASHWTNL|GLF_D3D_USINGHWTNL); 
  gl_ctMaxStreams = 16; // software T&L has enough streams
  extern INDEX d3d_bUseHardwareTnL;

  // cannot have HW VP if not supported by HW, right?
  if( d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
    gl_ulFlags |= GLF_D3D_HASHWTNL;
    gl_ctMaxStreams = d3dCaps.MaxStreams;
    if( gl_ctMaxStreams<GFX_MINSTREAMS) d3d_bUseHardwareTnL = 0; // cannot use HW T&L if not enough streams
    if( d3d_bUseHardwareTnL) {
      d3d_bUseHardwareTnL = 1; // clamp just in case
      dwVP = D3DCREATE_HARDWARE_VERTEXPROCESSING;
      gl_ulFlags |= GLF_D3D_USINGHWTNL;
    } // no HW T&L 
  } else d3d_bUseHardwareTnL = 0;

  // go for it ...
  extern HWND _hwndMain;
  extern const D3DDEVTYPE d3dDevType;
  hr = gl_pD3D->CreateDevice( iAdapter, d3dDevType, _hwndMain, dwVP, &d3dPresentParams, &gl_pd3dDevice);
  if( hr!=D3D_OK) return FALSE;
  gl_d3dColorFormat = d3dColorFormat;
  gl_d3dDepthFormat = d3dDepthFormat;
  gl_iCurrentDepth  = iZDepth;
  return TRUE;
}
    



// fallback D3D internal format
// (reverts to next format that closely matches requied one)
static D3DFORMAT FallbackFormat_D3D( D3DFORMAT eFormat, BOOL b2ndTry)
{
  switch( eFormat) {
  case D3DFMT_X8R8G8B8: return !b2ndTry ? D3DFMT_A8R8G8B8 : D3DFMT_R5G6B5;
  case D3DFMT_X1R5G5B5: return !b2ndTry ? D3DFMT_R5G6B5   : D3DFMT_A1R5G5B5;
  case D3DFMT_X4R4G4B4: return !b2ndTry ? D3DFMT_R5G6B5   : D3DFMT_A1R5G5B5;
  case D3DFMT_R5G6B5:   return !b2ndTry ? D3DFMT_X1R5G5B5 : D3DFMT_A1R5G5B5;
  case D3DFMT_L8:       return !b2ndTry ? D3DFMT_A8L8     : D3DFMT_X8R8G8B8;
  case D3DFMT_A8L8:     return D3DFMT_A8R8G8B8;
  case D3DFMT_A1R5G5B5: return D3DFMT_A4R4G4B4;
  case D3DFMT_A8R8G8B8: return D3DFMT_A4R4G4B4;
  case D3DFMT_DXT1:     return D3DFMT_A1R5G5B5;
  case D3DFMT_DXT3:     return D3DFMT_A4R4G4B4;
  case D3DFMT_DXT5:     return D3DFMT_A4R4G4B4;
  case D3DFMT_A4R4G4B4: // must have this one!
  default: ASSERTALWAYS( "Can't fallback texture format.");
  } // missed!
  return D3DFMT_UNKNOWN;
}


// find closest 
extern D3DFORMAT FindClosestFormat_D3D( D3DFORMAT d3df)
{
  FOREVER {
    if( HasTextureFormat_D3D(d3df)) return d3df;
    D3DFORMAT d3df2 = FallbackFormat_D3D( d3df, FALSE);
    if( HasTextureFormat_D3D(d3df2)) return d3df2;
    d3df = FallbackFormat_D3D( d3df, TRUE);
  }
}



// VERTEX/INDEX BUFFERS SUPPORT THRU STREAMS


// DEBUG helper
static void CheckStreams(void)
{
  UINT uiRet, ui;
  INDEX iRef, iPass;
  DWORD dwVS;
  HRESULT hr;
  LPDIRECT3DVERTEXBUFFER8 pVBRet, pVB;
  LPDIRECT3DINDEXBUFFER8 pIBRet;
  const LPDIRECT3DDEVICE8 pd3dDev = _pGfx->gl_pd3dDevice;

  // check passes and buffer position
  ASSERT( _iTexPass>=0 && _iColPass>=0);
  ASSERT( _iVtxPos >=0 && _iVtxPos<65536);

  // check vertex positions
  ASSERT( _ulStreamsMask & (1<<POSIDX)); // must be in shader!
  hr = pd3dDev->GetStreamSource( POSIDX, &pVBRet, &uiRet);
  D3D_CHECKERROR(hr);
  ASSERT( pVBRet!=NULL);
  iRef = pVBRet->Release();
  ASSERT( iRef==1 && pVBRet==_pGfx->gl_pd3dVtx && uiRet==POSSIZE);

  // check normals
  pVB = NULL;
  ui  = NORSIZE;
  hr  = pd3dDev->GetStreamSource( NORIDX, &pVBRet, &uiRet);
  D3D_CHECKERROR(hr);
  if( pVBRet!=NULL) iRef = pVBRet->Release();
  if( _ulStreamsMask & (1<<NORIDX)) pVB = _pGfx->gl_pd3dNor;
  ASSERT( iRef==1 && pVBRet==pVB && (uiRet==ui || uiRet==0));

  // check colors
  pVB = NULL;
  ui  = COLSIZE;
  hr  = pd3dDev->GetStreamSource( COLIDX, &pVBRet, &uiRet);
  D3D_CHECKERROR(hr);
  if( pVBRet!=NULL) iRef = pVBRet->Release();
  if( _ulStreamsMask & (1<<COLIDX)) {
    iPass = (_iColPass-1) % _pGfx->gl_ctColBuffers;
    pVB = _pGfx->gl_pd3dCol[iPass];
  }
  ASSERT( iRef==1 && pVBRet==pVB && (uiRet==ui || uiRet==0));

  // check 1st texture coords
  pVB = NULL;
  ui  = _bProjectiveMapping ? TX4SIZE : TEXSIZE;
  hr  = pd3dDev->GetStreamSource( TEXIDX, &pVBRet, &uiRet);
  D3D_CHECKERROR(hr);
  if( pVBRet!=NULL) iRef = pVBRet->Release();
  if( _ulStreamsMask & (1<<(TEXIDX))) {
    iPass = (_iTexPass-1) % _pGfx->gl_ctTexBuffers;
    pVB = _pGfx->gl_pd3dTex[iPass];
  }
  ASSERT( iRef==1 && pVBRet==pVB && (uiRet==ui || uiRet==0));
  
  // check indices
  hr = pd3dDev->GetIndices( &pIBRet, &uiRet);
  D3D_CHECKERROR(hr);
  ASSERT( pIBRet!=NULL);
  iRef = pIBRet->Release();
  ASSERT( iRef==1 && pIBRet==_pGfx->gl_pd3dIdx && uiRet==0);

  // check shader
  hr = pd3dDev->GetVertexShader( &dwVS);
  D3D_CHECKERROR(hr);
  ASSERT( dwVS!=NONE && dwVS==_pGfx->gl_dwVertexShader);

  /* check shader declaration (SEEMS LIKE THIS SHIT DOESN'T WORK!)
  const INDEX ctMaxDecls = 2*MAXSTREAMS+1;
  INDEX ctDecls = ctMaxDecls;
  DWORD adwDeclRet[ctMaxDecls];
  hr = pd3dDev->GetVertexShaderDeclaration( _pGfx->gl_dwVertexShader, (void*)&adwDeclRet[0], (DWORD*)&ctDecls);
  D3D_CHECKERROR(hr);
  ASSERT( ctDecls>0 && ctDecls<ctMaxDecls);
  INDEX iRet = memcmp( &adwDeclRet[0], &_adwCurrentDecl[0], ctDecls*sizeof(DWORD));
  ASSERT( iRet==0); */
}



// prepare vertex array for D3D (type 0=vtx, 1=nor, 2=col, 3=tex, 4=projtex)
extern void SetVertexArray_D3D( INDEX iType, ULONG *pulVtx)
{
  HRESULT hr;
  SLONG slStride;
  DWORD dwLockFlag;
  INDEX iStream, iThisPass;
  INDEX ctLockSize, iLockOffset;
  LPDIRECT3DVERTEXBUFFER8 pd3dVB;
  ASSERT( _iTexPass>=0 && _iColPass>=0);
  ASSERT( _iVtxPos >=0 && _iVtxPos<65536);
  const BOOL bHWTnL = _pGfx->gl_ulFlags & GLF_D3D_USINGHWTNL;

  // determine which buffer we work on
  switch(iType)
  {

  // VERTICES
  case 0: 
    // make sure that we have enough space in vertex buffers
    ASSERT(GFX_ctVertices>0);
    SetupVertexArrays_D3D( GFX_ctVertices * (bHWTnL?2:1));
    // determine lock type
    pd3dVB = _pGfx->gl_pd3dVtx;
    if( !bHWTnL || (_iVtxOffset+GFX_ctVertices)>=_pGfx->gl_ctVertices) {
       // reset pos and flags
      _iVtxOffset = 0;
      _dwVtxLockFlags = D3DLOCK_DISCARD;
      for( INDEX i=0; i<GFX_MAXLAYERS; i++) _dwColLockFlags[i] = _dwTexLockFlags[i] = _dwVtxLockFlags;
    } else _dwVtxLockFlags = D3DLOCK_NOOVERWRITE;
    // keep and advance current lock position
    _iVtxPos = _iVtxOffset;
    _iVtxOffset += GFX_ctVertices;
    // determine lock type, pos and size
    dwLockFlag  = _dwVtxLockFlags;
    ctLockSize  = GFX_ctVertices*4; 
    iLockOffset = _iVtxPos*4;
    // set stream params
    iStream  = POSIDX;
    slStride = POSSIZE;
    // reset array states
    _ulStreamsMask = NONE;
    _bProjectiveMapping = FALSE;
    _iTexPass = _iColPass = 0;
    break;

  // NORMALS
  case 1: 
    ASSERT( _pGfx->gl_iMaxTessellationLevel>0 && gap_iTruformLevel>0); // only if enabled
    pd3dVB = _pGfx->gl_pd3dNor;
    ASSERT( _iTexPass<2 && _iColPass<2);  // normals must be set in 1st pass (completed or not)
    // determine lock type, pos and size
    dwLockFlag  = _dwVtxLockFlags;
    ctLockSize  = GFX_ctVertices*4; 
    iLockOffset = _iVtxPos*4; 
    // set stream params
    iStream  = NORIDX;
    slStride = NORSIZE;
    break;

  // COLORS
  case 2: 
    iThisPass = _iColPass;
    // restart in case of too many passes
    if( iThisPass>=_pGfx->gl_ctColBuffers) {
      dwLockFlag = D3DLOCK_DISCARD;
      iThisPass %= _pGfx->gl_ctColBuffers;
    } else { // continue in case of enough buffers
      dwLockFlag = _dwColLockFlags[iThisPass];
    } // mark
    _dwColLockFlags[iThisPass] = D3DLOCK_NOOVERWRITE;
    ASSERT( iThisPass>=0 && iThisPass<_pGfx->gl_ctColBuffers);
    pd3dVB = _pGfx->gl_pd3dCol[iThisPass];
    // determine lock pos and size
    ctLockSize  = GFX_ctVertices*1; 
    iLockOffset = _iVtxPos*1; 
    // set stream params
    iStream  = COLIDX;
    slStride = COLSIZE;
    _iColPass++; // advance to next color pass
    break;

  // PROJECTIVE TEXTURE COORDINATES
  case 4:
    _bProjectiveMapping = TRUE;
    // fall thru ...

  // TEXTURE COORDINATES
  case 3:
    iThisPass = _iTexPass;
    // restart in case of too many passes
    if( iThisPass>=_pGfx->gl_ctTexBuffers) {
      dwLockFlag = D3DLOCK_DISCARD;
      iThisPass %= _pGfx->gl_ctTexBuffers;
    } else { // continue in case of enough buffers
      dwLockFlag = _dwTexLockFlags[iThisPass];
    } // mark
    _dwTexLockFlags[iThisPass] = D3DLOCK_NOOVERWRITE;
    ASSERT( iThisPass>=0 && iThisPass<_pGfx->gl_ctTexBuffers);
    pd3dVB = _pGfx->gl_pd3dTex[iThisPass];
    // set stream number (must take into account tex-unit, because of multi-texturing!)
    iStream = TEXIDX +GFX_iActiveTexUnit;
    // determine stride, lock pos and size
    if( _bProjectiveMapping) {
      ctLockSize  = GFX_ctVertices*4; 
      iLockOffset = _iVtxPos*4; 
      slStride = TX4SIZE;
    } else {
      ctLockSize  = GFX_ctVertices*2; 
      iLockOffset = _iVtxPos*2; 
      slStride = TEXSIZE;
    } // advance to next texture pass
    _iTexPass++; 
    break;

  // BUF! WRONG.
  default: ASSERTALWAYS( "SetVertexArray_D3D: wrong stream number!");
  }

  ASSERT( _iTexPass>=0 && _iColPass>=0);
  ASSERT( _iVtxPos >=0 && _iVtxPos<65536);

  // fetch D3D buffer
  ULONG *pulLockedBuffer;
  hr = pd3dVB->Lock( iLockOffset*sizeof(ULONG), ctLockSize*sizeof(ULONG), (UBYTE**)&pulLockedBuffer, dwLockFlag);
  D3D_CHECKERROR(hr);

  // copy (or convert) vertices there and unlock
  ASSERT(pulVtx!=NULL); 
  if( iType!=2) CopyLongs( pulVtx, pulLockedBuffer, ctLockSize); // vertex array
  else          abgr2argb( pulVtx, pulLockedBuffer, ctLockSize); // color array (needs conversion)

  // done
  hr = pd3dVB->Unlock();
  D3D_CHECKERROR(hr);

  // update streams mask and assign 
  _ulStreamsMask |= 1<<iStream;
  hr = _pGfx->gl_pd3dDevice->SetStreamSource( iStream, pd3dVB, slStride);
  D3D_CHECKERROR(hr);
}



// prepare and draw arrays
extern void DrawElements_D3D( INDEX ctIndices, INDEX *pidx)
{
  // paranoid & sunburnt (by Skunk Anansie:)
  ASSERT( _iTexPass>=0 && _iColPass>=0);
  ASSERT( _iVtxPos >=0 && _iVtxPos<65536);
  const LPDIRECT3DDEVICE8 pd3dDev = _pGfx->gl_pd3dDevice;

  // at least one triangle must be sent
  ASSERT( ctIndices>=3 && ((ctIndices/3)*3)==ctIndices);
  if( ctIndices<3) return;
  extern INDEX d3d_iVertexRangeTreshold;
  d3d_iVertexRangeTreshold = Clamp( d3d_iVertexRangeTreshold, 0L, 9999L);

  // eventually adjust size of index buffer
  const BOOL bHWTnL = _pGfx->gl_ulFlags & GLF_D3D_USINGHWTNL;
  SetupIndexArray_D3D( ctIndices * (bHWTnL?2:1));

  // determine lock position and type
  if( (_iIdxOffset+ctIndices) >= _pGfx->gl_ctIndices) _iIdxOffset = 0;
  const DWORD dwLockFlag   = (_iIdxOffset>0) ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD;
  const SLONG slLockSize   =  ctIndices *IDXSIZE; 
  const SLONG slLockOffset = _iIdxOffset*IDXSIZE; 

  // copy indices to index buffer
  HRESULT hr;
  UWORD *puwLockedBuffer;
  hr = _pGfx->gl_pd3dIdx->Lock( slLockOffset, slLockSize, (UBYTE**)&puwLockedBuffer, dwLockFlag);
  D3D_CHECKERROR(hr);

  INDEX iMinIndex = 65536;
  INDEX iMaxIndex = 0;
  const BOOL bSetRange = !(_pGfx->gl_ulFlags&GLF_D3D_USINGHWTNL) && (GFX_ctVertices>d3d_iVertexRangeTreshold);
  ASSERT( _iVtxPos>=0 && _iVtxPos<65536);

#if ASMOPT == 1
  const __int64 mmSignD = 0x0000800000008000;
  const __int64 mmSignW = 0x8000800080008000;
  __asm {
    // adjust 32-bit and copy to 16-bit array
    mov     esi,D [pidx]
    mov     edi,D [puwLockedBuffer]
    mov     ecx,D [ctIndices]
    shr     ecx,2   // 4 by 4
    jz      elemL2
    movd    mm7,D [_iVtxPos]
    punpcklwd mm7,mm7
    punpckldq mm7,mm7   // MM7 = vtxPos | vtxPos || vtxPos | vtxPos
    paddw   mm7,Q [mmSignW]
elemCLoop:
    movq    mm1,Q [esi+0]
    movq    mm2,Q [esi+8]
    psubd   mm1,Q [mmSignD]
    psubd   mm2,Q [mmSignD]
    packssdw mm1,mm2
    paddw   mm1,mm7
    movq    Q [edi],mm1
    add     esi,4*4
    add     edi,2*4
    dec     ecx
    jnz     elemCLoop
    emms
elemL2:
    test    D [ctIndices],2
    jz      elemL1
    mov     eax,D [esi+0]
    mov     edx,D [esi+4]
    add     eax,D [_iVtxPos]
    add     edx,D [_iVtxPos]
    shl     edx,16
    or      eax,edx
    mov     D [edi],eax
    add     esi,4*2
    add     edi,2*2
elemL1:
    test    D [ctIndices],1
    jz      elemRange
    mov     eax,D [esi]
    add     eax,D [_iVtxPos]
    mov     W [edi],ax

elemRange:
    // find min/max index (if needed)
    cmp     D [bSetRange],0
    jz      elemEnd

    mov     edi,D [iMinIndex]
    mov     edx,D [iMaxIndex]
    mov     esi,D [pidx]
    mov     ecx,D [ctIndices]
elemTLoop:
    mov     eax,D [esi]
    add     eax,D [_iVtxPos]
    cmp     eax,edi
    cmovl   edi,eax
    cmp     eax,edx
    cmovg   edx,eax
    add     esi,4
    dec     ecx
    jnz     elemTLoop
    mov     D [iMinIndex],edi 
    mov     D [iMaxIndex],edx
elemEnd:
  }
#else
  for( INDEX idx=0; idx<ctIndices; idx++) {
    const INDEX iAdj = pidx[idx] + _iVtxPos;
         if( iMinIndex>iAdj) iMinIndex = iAdj;
    else if( iMaxIndex<iAdj) iMaxIndex = iAdj;
    puwLockedBuffer[idx] = iAdj;
  }
#endif

  // indices filled
  hr = _pGfx->gl_pd3dIdx->Unlock();
  D3D_CHECKERROR(hr);

  // check whether to use color array or not
  if( GFX_bColorArray) _ulStreamsMask |= (1<<COLIDX);
  else _ulStreamsMask &= ~(1<<COLIDX);

  // must adjust some stuff when projective mapping usage has been toggled
  if( !_bLastProjectiveMapping != !_bProjectiveMapping) {
    _bLastProjectiveMapping = _bProjectiveMapping;
    D3DTEXTURETRANSFORMFLAGS ttf;
    if( _bProjectiveMapping) {
      _ulStreamsMask |= 0x1000;
      ttf = D3DTTFF_PROJECTED;
    } else ttf = D3DTTFF_DISABLE;
    hr = pd3dDev->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, ttf);
    D3D_CHECKERROR(hr);
  }

  // eventually (re)construct vertex shader out of streams' bit-mask
  if( _ulLastStreamsMask != _ulStreamsMask)
  { // reset streams that were used before
    ULONG ulThisMask = _ulStreamsMask;
    ULONG ulLastMask = _ulLastStreamsMask;
    for( INDEX iStream=0; iStream<MAXSTREAMS; iStream++) {
      if( (ulThisMask&1)==0 && (ulLastMask&1)!=0) {
        hr = pd3dDev->SetStreamSource( iStream,NULL,0);
        D3D_CHECKERROR(hr);
      } // next stream
      ulThisMask >>= 1;
      ulLastMask >>= 1;
    }
    // setup new vertex shader
    _dwCurrentVS = SetupShader_D3D(_ulStreamsMask);
    _ulLastStreamsMask = _ulStreamsMask;
  } 

  // (re)set vertex shader
  ASSERT(_dwCurrentVS!=NONE);
  if( _pGfx->gl_dwVertexShader!=_dwCurrentVS) {
    hr = _pGfx->gl_pd3dDevice->SetVertexShader(_dwCurrentVS);
    D3D_CHECKERROR(hr);
   _pGfx->gl_dwVertexShader = _dwCurrentVS;
  }

#ifndef NDEBUG
  // Paranoid Android (by Radiohead:)
  CheckStreams();
#endif

  // determine vertex range  
  INDEX iVtxStart, ctVtxUsed;
  // if not too much vertices in buffer
  if( !bSetRange) {
    // set whole vertex buffer
    iVtxStart = _iVtxPos;
    ctVtxUsed = GFX_ctVertices;
  // if lotta vertices in buffer
  } else {
    // set only part of vertex buffer
    iVtxStart = iMinIndex;            
    ctVtxUsed = iMaxIndex-iMinIndex+1;
    ASSERT( iMinIndex<iMaxIndex);
  }

  // draw indices
  hr = pd3dDev->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, iVtxStart, ctVtxUsed, _iIdxOffset, ctIndices/3);
  D3D_CHECKERROR(hr);
  // move to next available lock position
  _iIdxOffset += ctIndices;
}

#endif // SE1_D3D
