/* 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. */



// ENABLE/DISABLE FUNCTIONS


static void ogl_EnableTexture(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  bRes = pglIsEnabled(GL_TEXTURE_2D);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_abTexture[GFX_iActiveTexUnit]);
#endif

  // cached?
  if( GFX_abTexture[GFX_iActiveTexUnit] && gap_bOptimizeStateChanges) return;
  GFX_abTexture[GFX_iActiveTexUnit] = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable(GL_TEXTURE_2D);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_DisableTexture(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_TEXTURE_2D);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_abTexture[GFX_iActiveTexUnit]);
#endif

  // cached?
  if( !GFX_abTexture[GFX_iActiveTexUnit] && gap_bOptimizeStateChanges) return;
  GFX_abTexture[GFX_iActiveTexUnit] = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable(GL_TEXTURE_2D);
  pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_EnableDepthTest(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  bRes = pglIsEnabled(GL_DEPTH_TEST);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bDepthTest);
#endif
  // cached?
  if( GFX_bDepthTest && gap_bOptimizeStateChanges) return;
  GFX_bDepthTest = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable( GL_DEPTH_TEST);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}


static void ogl_DisableDepthTest(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_DEPTH_TEST);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bDepthTest);
#endif

  // cached?
  if( !GFX_bDepthTest && gap_bOptimizeStateChanges) return;
  GFX_bDepthTest = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable(GL_DEPTH_TEST);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}


static void ogl_EnableDepthBias(void)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable( GL_POLYGON_OFFSET_POINT);
  pglEnable( GL_POLYGON_OFFSET_LINE);
  pglEnable( GL_POLYGON_OFFSET_FILL);
  pglPolygonOffset( -1.0f, -2.0f);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}


static void ogl_DisableDepthBias(void)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable( GL_POLYGON_OFFSET_POINT);
  pglDisable( GL_POLYGON_OFFSET_LINE);
  pglDisable( GL_POLYGON_OFFSET_FILL);
  pglPolygonOffset( 0.0f, 0.0f);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_EnableDepthWrite(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  pglGetIntegerv( GL_DEPTH_WRITEMASK, (GLint*)&bRes);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bDepthWrite);
#endif

  // cached?
  if( GFX_bDepthWrite && gap_bOptimizeStateChanges) return;
  GFX_bDepthWrite = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDepthMask(GL_TRUE);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_DisableDepthWrite(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  pglGetIntegerv( GL_DEPTH_WRITEMASK, (GLint*)&bRes);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bDepthWrite);
#endif

  // cached?
  if( !GFX_bDepthWrite && gap_bOptimizeStateChanges) return;
  GFX_bDepthWrite = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDepthMask(GL_FALSE);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_EnableDither(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  bRes = pglIsEnabled(GL_DITHER);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bDithering);
#endif

  // cached?
  if( GFX_bDithering && gap_bOptimizeStateChanges) return;
  GFX_bDithering = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable(GL_DITHER);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_DisableDither(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_DITHER);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bDithering);
#endif

  // cached?
  if( !GFX_bDithering && gap_bOptimizeStateChanges) return;
  GFX_bDithering = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable(GL_DITHER);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_EnableAlphaTest(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  bRes = pglIsEnabled(GL_ALPHA_TEST);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bAlphaTest);
#endif

  // cached?
  if( GFX_bAlphaTest && gap_bOptimizeStateChanges) return;
  GFX_bAlphaTest = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable(GL_ALPHA_TEST);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_DisableAlphaTest(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_ALPHA_TEST);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bAlphaTest);
#endif

  // cached?
  if( !GFX_bAlphaTest && gap_bOptimizeStateChanges) return;
  GFX_bAlphaTest = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable(GL_ALPHA_TEST);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_EnableBlend(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  bRes = pglIsEnabled(GL_BLEND);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bBlending);
#endif
  // cached?
  if( GFX_bBlending && gap_bOptimizeStateChanges) return;
  GFX_bBlending = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable( GL_BLEND);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);

  // adjust dithering
  if( gap_iDithering==2) ogl_EnableDither();
  else ogl_DisableDither();
}



static void ogl_DisableBlend(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_BLEND);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bBlending);
#endif

  // cached?
  if( !GFX_bBlending && gap_bOptimizeStateChanges) return;
  GFX_bBlending = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable(GL_BLEND);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);

  // adjust dithering
  if( gap_iDithering==0) ogl_DisableDither();
  else ogl_EnableDither();
}



static void ogl_EnableClipping(void)
{
  // only if supported
  if( !(_pGfx->gl_ulFlags&GLF_EXT_CLIPHINT)) return;

  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  pglGetIntegerv( GL_CLIP_VOLUME_CLIPPING_HINT_EXT, (GLint*)&bRes);
  bRes = (bRes==GL_FASTEST) ? FALSE : TRUE;
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bClipping);
#endif

  // cached?
  if( GFX_bClipping && gap_bOptimizeStateChanges) return;
  GFX_bClipping = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglHint( GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_DONT_CARE);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_DisableClipping(void)
{
  // only if allowed and supported
  if( gap_iOptimizeClipping<2 || !(_pGfx->gl_ulFlags&GLF_EXT_CLIPHINT)) return;

  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes;
  pglGetIntegerv( GL_CLIP_VOLUME_CLIPPING_HINT_EXT, (GLint*)&bRes);
  bRes = (bRes==GL_FASTEST) ? FALSE : TRUE;
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bClipping);
#endif

  // cached?
  if( !GFX_bClipping && gap_bOptimizeStateChanges) return;
  GFX_bClipping = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglHint( GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_FASTEST);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}




static void ogl_EnableClipPlane(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_CLIP_PLANE0);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bClipPlane);
#endif
  // cached?
  if( GFX_bClipPlane && gap_bOptimizeStateChanges) return;
  GFX_bClipPlane = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable( GL_CLIP_PLANE0);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



static void ogl_DisableClipPlane(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_CLIP_PLANE0);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bClipPlane);
#endif
  // cached?
  if( !GFX_bClipPlane && gap_bOptimizeStateChanges) return;
  GFX_bClipPlane = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable(GL_CLIP_PLANE0);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// enable usage of color array for subsequent rendering
static void ogl_EnableColorArray(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_COLOR_ARRAY);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bColorArray);
#endif

  // cached?
  if( GFX_bColorArray && gap_bOptimizeStateChanges) return;
  GFX_bColorArray = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnableClientState(GL_COLOR_ARRAY);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// enable usage of constant color for subsequent rendering
static void ogl_DisableColorArray(void)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_COLOR_ARRAY);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bColorArray);
#endif

  // cached?
  if( !GFX_bColorArray && gap_bOptimizeStateChanges) return;
  GFX_bColorArray = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisableClientState(GL_COLOR_ARRAY);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// enable truform rendering
static void ogl_EnableTruform(void)
{
  // skip if Truform isn't set
  if( truform_iLevel<1) return;

  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_PN_TRIANGLES_ATI);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bTruform);
#endif

  if( GFX_bTruform && gap_bOptimizeStateChanges) return;
  GFX_bTruform = TRUE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnable( GL_PN_TRIANGLES_ATI); 
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// disable truform rendering
static void ogl_DisableTruform(void)
{
  // skip if Truform isn't set
  if( truform_iLevel<1) return;

  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  BOOL bRes; 
  bRes = pglIsEnabled(GL_PN_TRIANGLES_ATI);
  OGL_CHECKERROR;
  ASSERT( !bRes == !GFX_bTruform);
#endif

  if( !GFX_bTruform && gap_bOptimizeStateChanges) return;
  GFX_bTruform = FALSE;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisable( GL_PN_TRIANGLES_ATI); 
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// helper for blending operation function
__forceinline GLenum BlendToOGL( GfxBlend eFunc) {
  switch( eFunc) {
  case GFX_ZERO:          return GL_ZERO;
  case GFX_ONE:           return GL_ONE;
  case GFX_SRC_COLOR:     return GL_SRC_COLOR;
  case GFX_INV_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR;
  case GFX_DST_COLOR:     return GL_DST_COLOR;          
  case GFX_INV_DST_COLOR: return GL_ONE_MINUS_DST_COLOR;
  case GFX_SRC_ALPHA:     return GL_SRC_ALPHA;          
  case GFX_INV_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA;
  default: ASSERTALWAYS("Invalid GFX blending function!");
  } return GL_ONE;
}

__forceinline GfxBlend BlendFromOGL( GLenum gFunc) {
  switch( gFunc) {
  case GL_ZERO:                return GFX_ZERO;
  case GL_ONE:                 return GFX_ONE;
  case GL_SRC_COLOR:           return GFX_SRC_COLOR;
  case GL_ONE_MINUS_SRC_COLOR: return GFX_INV_SRC_COLOR;
  case GL_DST_COLOR:           return GFX_DST_COLOR;
  case GL_ONE_MINUS_DST_COLOR: return GFX_INV_DST_COLOR;
  case GL_SRC_ALPHA:           return GFX_SRC_ALPHA;
  case GL_ONE_MINUS_SRC_ALPHA: return GFX_INV_SRC_ALPHA;
  default: ASSERTALWAYS("Unsupported OGL blending function!");
  } return GFX_ONE;
}


// set blending operations
static void ogl_BlendFunc( GfxBlend eSrc, GfxBlend eDst)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  GLenum gleSrc, gleDst;
#ifndef NDEBUG
  GfxBlend gfxSrc, gfxDst; 
  pglGetIntegerv( GL_BLEND_SRC, (GLint*)&gleSrc);
  pglGetIntegerv( GL_BLEND_DST, (GLint*)&gleDst);
  OGL_CHECKERROR;
  gfxSrc = BlendFromOGL(gleSrc);
  gfxDst = BlendFromOGL(gleDst);
  ASSERT( gfxSrc==GFX_eBlendSrc && gfxDst==GFX_eBlendDst);
#endif
  // cached?
  if( eSrc==GFX_eBlendSrc && eDst==GFX_eBlendDst && gap_bOptimizeStateChanges) return;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  gleSrc = BlendToOGL(eSrc);
  gleDst = BlendToOGL(eDst);
  pglBlendFunc( gleSrc, gleDst);
  OGL_CHECKERROR;
  // done
  GFX_eBlendSrc = eSrc;
  GFX_eBlendDst = eDst;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// color buffer writing enable
static void ogl_SetColorMask( ULONG ulColorMask)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  _ulCurrentColorMask = ulColorMask; // keep for Get...()
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  const BOOL bR = (ulColorMask&CT_RMASK) == CT_RMASK;
  const BOOL bG = (ulColorMask&CT_GMASK) == CT_GMASK;
  const BOOL bB = (ulColorMask&CT_BMASK) == CT_BMASK;
  const BOOL bA = (ulColorMask&CT_AMASK) == CT_AMASK;
  pglColorMask( bR,bG,bB,bA);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// helper for depth compare function
__forceinline GLenum CompToOGL( GfxComp eFunc) {
  switch( eFunc) {
  case GFX_NEVER:         return GL_NEVER;
  case GFX_LESS:          return GL_LESS;
  case GFX_LESS_EQUAL:    return GL_LEQUAL;
  case GFX_EQUAL:         return GL_EQUAL;
  case GFX_NOT_EQUAL:     return GL_NOTEQUAL;
  case GFX_GREATER_EQUAL: return GL_GEQUAL;
  case GFX_GREATER:       return GL_GREATER;
  case GFX_ALWAYS:        return GL_ALWAYS;
  default: ASSERTALWAYS("Invalid GFX compare function!");
  } return GL_ALWAYS;
}
  
__forceinline GfxComp CompFromOGL( GLenum gFunc) {
  switch( gFunc) {
  case GL_NEVER:    return GFX_NEVER;
  case GL_LESS:     return GFX_LESS;
  case GL_LEQUAL:   return GFX_LESS_EQUAL;
  case GL_EQUAL:    return GFX_EQUAL;
  case GL_NOTEQUAL: return GFX_NOT_EQUAL;
  case GL_GEQUAL:   return GFX_GREATER_EQUAL;
  case GL_GREATER:  return GFX_GREATER;
  case GL_ALWAYS:   return GFX_ALWAYS;
  default: ASSERTALWAYS("Invalid OGL compare function!");
  } return GFX_ALWAYS;
}



// set depth buffer compare mode
static void ogl_DepthFunc( GfxComp eFunc)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  GLenum gleFunc;
#ifndef NDEBUG
  GfxComp gfxFunc; 
  pglGetIntegerv( GL_DEPTH_FUNC, (GLint*)&gleFunc);
  OGL_CHECKERROR;
  gfxFunc = CompFromOGL( gleFunc);
  ASSERT( gfxFunc==GFX_eDepthFunc);
#endif
  // cached?
  if( eFunc==GFX_eDepthFunc && gap_bOptimizeStateChanges) return;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  gleFunc = CompToOGL(eFunc);
  pglDepthFunc(gleFunc);
  OGL_CHECKERROR;
  GFX_eDepthFunc = eFunc;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}


    
// set depth buffer range
static void ogl_DepthRange( FLOAT fMin, FLOAT fMax)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  FLOAT fDepths[2]; 
  pglGetFloatv( GL_DEPTH_RANGE,(GLfloat*)&fDepths);
  OGL_CHECKERROR;
  ASSERT( fDepths[0]==GFX_fMinDepthRange && fDepths[1]==GFX_fMaxDepthRange);
#endif

  // cached?
  if( GFX_fMinDepthRange==fMin && GFX_fMaxDepthRange==fMax && gap_bOptimizeStateChanges) return;
  GFX_fMinDepthRange = fMin;
  GFX_fMaxDepthRange = fMax;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDepthRange( fMin, fMax); 
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set face culling
static void ogl_CullFace( GfxFace eFace)
{
  // check consistency and face
  ASSERT( eFace==GFX_FRONT || eFace==GFX_BACK || eFace==GFX_NONE);
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  GLenum gleCull;
  BOOL bRes = pglIsEnabled(GL_CULL_FACE);
  pglGetIntegerv( GL_CULL_FACE_MODE, (GLint*)&gleCull);
  OGL_CHECKERROR;
  ASSERT( (bRes==GL_FALSE && GFX_eCullFace==GFX_NONE)
       || (bRes==GL_TRUE  && gleCull==GL_FRONT && GFX_eCullFace==GFX_FRONT)
       || (bRes==GL_TRUE  && gleCull==GL_BACK  && GFX_eCullFace==GFX_BACK));
#endif
  // cached?
  if( GFX_eCullFace==eFace && gap_bOptimizeStateChanges) return;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  if( eFace==GFX_FRONT) {
    if( GFX_eCullFace==GFX_NONE) pglEnable(GL_CULL_FACE);
    pglCullFace(GL_FRONT);
  } else if( eFace==GFX_BACK) {
    if( GFX_eCullFace==GFX_NONE) pglEnable(GL_CULL_FACE);
    pglCullFace(GL_BACK);
  } else {
    pglDisable(GL_CULL_FACE);
  }
  OGL_CHECKERROR;
  GFX_eCullFace = eFace;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set front face
static void ogl_FrontFace( GfxFace eFace)
{
  // check consistency and face
  ASSERT( eFace==GFX_CW || eFace==GFX_CCW);
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  GLenum gleFace;
  pglGetIntegerv( GL_FRONT_FACE, (GLint*)&gleFace);
  OGL_CHECKERROR;
  ASSERT( (gleFace==GL_CCW &&  GFX_bFrontFace)
       || (gleFace==GL_CW  && !GFX_bFrontFace));
#endif
  // cached?
  BOOL bFrontFace = (eFace==GFX_CCW);
  if( !bFrontFace==!GFX_bFrontFace && gap_bOptimizeStateChanges) return;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  if( eFace==GFX_CCW) {
    pglFrontFace( GL_CCW);
  } else {
    pglFrontFace( GL_CW);
  }
  OGL_CHECKERROR;
  GFX_bFrontFace = bFrontFace; 

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set custom clip plane 
static void ogl_ClipPlane( const DOUBLE *pdViewPlane)
{
  // check API and plane
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL && pdViewPlane!=NULL);

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglClipPlane( GL_CLIP_PLANE0, pdViewPlane);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set texture matrix
static void ogl_SetTextureMatrix( const FLOAT *pfMatrix/*=NULL*/)
{
  // check API
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  // set matrix
  pglMatrixMode(GL_TEXTURE);
  if( pfMatrix!=NULL) pglLoadMatrixf(pfMatrix);
  else pglLoadIdentity();
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}




// set view matrix 
static void ogl_SetViewMatrix( const FLOAT *pfMatrix/*=NULL*/)
{
  // check API
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);

  // cached? (only identity matrix)
  if( pfMatrix==NULL && GFX_bViewMatrix==NONE && gap_bOptimizeStateChanges) return;
  GFX_bViewMatrix = (pfMatrix!=NULL);

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  // set matrix
  pglMatrixMode( GL_MODELVIEW);
  if( pfMatrix!=NULL) pglLoadMatrixf(pfMatrix);
  else pglLoadIdentity();
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set orthographic matrix
static void ogl_SetOrtho( const FLOAT fLeft,   const FLOAT fRight, const FLOAT fTop,
                          const FLOAT fBottom, const FLOAT fNear,  const FLOAT fFar,
                          const BOOL bSubPixelAdjust/*=FALSE*/)
{
  // check API and matrix type
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);

  // cached?
  if( GFX_fLastL==fLeft  && GFX_fLastT==fTop    && GFX_fLastN==fNear
   && GFX_fLastR==fRight && GFX_fLastB==fBottom && GFX_fLastF==fFar && gap_bOptimizeStateChanges) return;
  GFX_fLastL = fLeft;   GFX_fLastT = fTop;     GFX_fLastN = fNear;
  GFX_fLastR = fRight;  GFX_fLastB = fBottom;  GFX_fLastF = fFar;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  // set matrix
  pglMatrixMode( GL_PROJECTION);
  pglLoadIdentity();
  pglOrtho( fLeft, fRight, fBottom, fTop, fNear, fFar);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set frustrum matrix
static void ogl_SetFrustum( const FLOAT fLeft, const FLOAT fRight,
                            const FLOAT fTop,  const FLOAT fBottom,
                            const FLOAT fNear, const FLOAT fFar)
{
  // check API
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);

  // cached?
  if( GFX_fLastL==-fLeft  && GFX_fLastT==-fTop    && GFX_fLastN==-fNear
   && GFX_fLastR==-fRight && GFX_fLastB==-fBottom && GFX_fLastF==-fFar && gap_bOptimizeStateChanges) return;
  GFX_fLastL = -fLeft;   GFX_fLastT = -fTop;     GFX_fLastN = -fNear;
  GFX_fLastR = -fRight;  GFX_fLastB = -fBottom;  GFX_fLastF = -fFar;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  // set matrix
  pglMatrixMode( GL_PROJECTION);
  pglLoadIdentity();
  pglFrustum( fLeft, fRight, fBottom, fTop, fNear, fFar);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set polygon mode (point, line or fill)
static void ogl_PolygonMode( GfxPolyMode ePolyMode)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  switch(ePolyMode) {
  case GFX_POINT:  pglPolygonMode( GL_FRONT_AND_BACK, GL_POINT);  break;
  case GFX_LINE:   pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE);   break;
  case GFX_FILL:   pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL);   break;
  default:  ASSERTALWAYS("Wrong polygon mode!");
  } // check
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}




// TEXTURE MANAGEMENT


// set texture wrapping mode
static void ogl_SetTextureWrapping( enum GfxWrap eWrapU, enum GfxWrap eWrapV)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  // check texture unit consistency
  GLint gliRet;
  pglGetIntegerv( GL_ACTIVE_TEXTURE_ARB, &gliRet);
  ASSERT( GFX_iActiveTexUnit==(gliRet-GL_TEXTURE0_ARB));
  pglGetIntegerv( GL_CLIENT_ACTIVE_TEXTURE_ARB, &gliRet);
  ASSERT( GFX_iActiveTexUnit==(gliRet-GL_TEXTURE0_ARB));
#endif

  _tpGlobal[GFX_iActiveTexUnit].tp_eWrapU = eWrapU;
  _tpGlobal[GFX_iActiveTexUnit].tp_eWrapV = eWrapV;
}



// set texture modulation mode
static void ogl_SetTextureModulation( INDEX iScale)
{
  // check consistency
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG                 
  // check current modulation
  GLint iRet;
  pglGetTexEnviv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &iRet);
  ASSERT( (GFX_iTexModulation[GFX_iActiveTexUnit]==1 && iRet==GL_MODULATE)
       || (GFX_iTexModulation[GFX_iActiveTexUnit]==2 && iRet==GL_COMBINE_EXT));
  OGL_CHECKERROR;
#endif
  
  // cached?
  ASSERT( iScale==1 || iScale==2);
  if( GFX_iTexModulation[GFX_iActiveTexUnit]==iScale) return;
  GFX_iTexModulation[GFX_iActiveTexUnit] = iScale;

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  if( iScale==2) {
    pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
    pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT,  GL_MODULATE);
    pglTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_EXT,    2.0f);
  } else {
    pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  } OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// generate texture for API
static void ogl_GenerateTexture( ULONG &ulTexObject)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  _sfStats.StartTimer(CStatForm::STI_BINDTEXTURE);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglGenTextures( 1, (GLuint*)&ulTexObject);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_BINDTEXTURE);
  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}


 
// unbind texture from API
static void ogl_DeleteTexture( ULONG &ulTexObject)
{
  // skip if already unbound
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  if( ulTexObject==NONE) return;

  _sfStats.StartTimer(CStatForm::STI_BINDTEXTURE);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDeleteTextures( 1, (GLuint*)&ulTexObject);
  ulTexObject = NONE;

  _sfStats.StopTimer(CStatForm::STI_BINDTEXTURE);
  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// VERTEX ARRAYS


// prepare vertex array for API
static void ogl_SetVertexArray( GFXVertex4 *pvtx, INDEX ctVtx)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  ASSERT( ctVtx>0 && pvtx!=NULL && GFX_iActiveTexUnit==0);
  GFX_ctVertices = ctVtx;
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglDisableClientState( GL_TEXTURE_COORD_ARRAY);
  pglDisableClientState( GL_COLOR_ARRAY); 
  pglDisableClientState( GL_NORMAL_ARRAY);
  ASSERT( !pglIsEnabled( GL_TEXTURE_COORD_ARRAY));
  ASSERT( !pglIsEnabled( GL_COLOR_ARRAY));
  ASSERT( !pglIsEnabled( GL_NORMAL_ARRAY));
  ASSERT(  pglIsEnabled( GL_VERTEX_ARRAY));
  pglVertexPointer( 3, GL_FLOAT, sizeof (*pvtx), pvtx);
  OGL_CHECKERROR;
  GFX_bColorArray = FALSE; // mark that color array has been disabled (because of potential LockArrays)

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// prepare normal array for API
static void ogl_SetNormalArray( GFXNormal *pnor)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  ASSERT( pnor!=NULL);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnableClientState(GL_NORMAL_ARRAY);
  ASSERT( pglIsEnabled(GL_NORMAL_ARRAY));
  pglNormalPointer( GL_FLOAT, sizeof (*pnor), pnor);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// prepare color array for API (and force rendering with color array!)
static void ogl_SetColorArray( GFXColor *pcol)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  ASSERT( pcol!=NULL);
  ogl_EnableColorArray();
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);
  
  pglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof (*pcol), pcol);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// prepare texture coordinates array for API
static void ogl_SetTexCoordArray( GFXTexCoord *ptex, BOOL b4/*=FALSE*/)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  ASSERT( ptex!=NULL);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
  ASSERT( pglIsEnabled(GL_TEXTURE_COORD_ARRAY));
  pglTexCoordPointer( b4?4:2, GL_FLOAT, sizeof (*ptex), ptex);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// set constant color (and force rendering w/o color array!)
static void ogl_SetConstantColor( COLOR col)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  ogl_DisableColorArray();
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  glCOLOR(col);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// draw prepared arrays
static void ogl_DrawElements( INDEX ctElem, INDEX *pidx)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
#ifndef NDEBUG
  // check if all indices are inside lock count (or smaller than 65536)
  if( pidx!=NULL) for( INDEX i=0; i<ctElem; i++) ASSERT( pidx[i] < GFX_ctVertices);
#endif

  _sfStats.StartTimer(CStatForm::STI_GFXAPI);
  _pGfx->gl_ctTotalTriangles += ctElem/3;  // for profiling

  // arrays or elements
  if( pidx==NULL) pglDrawArrays( GL_QUADS, 0, ctElem);
  else pglDrawElements( GL_TRIANGLES, ctElem, GL_UNSIGNED_INT, pidx);
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}



// force finish of API rendering queue
static void ogl_Finish(void)
{
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  _sfStats.StartTimer(CStatForm::STI_GFXAPI);

  pglFinish();
  OGL_CHECKERROR;

  _sfStats.StopTimer(CStatForm::STI_GFXAPI);
}




// routines for locking and unlocking compiled vertex arrays
static void ogl_LockArrays(void)
{
  // only if supported
  ASSERT( _pGfx->gl_eCurrentAPI==GAT_OGL);
  ASSERT( GFX_ctVertices>0 && !_bCVAReallyLocked);
  if( !(_pGfx->gl_ulFlags&GLF_EXT_COMPILEDVERTEXARRAY)) return;
  pglLockArraysEXT( 0, GFX_ctVertices);
  OGL_CHECKERROR;
 _bCVAReallyLocked = TRUE;
}
