/* -*- mode: C; tab-width:8;  -*-

             fxdd.c - 3Dfx VooDoo Mesa device driver functions
*/

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * See the file fxapi.c for more informations about authors
 *
 */


#ifdef HAVE_CONFIG_H
#include "conf.h"
#endif

#if defined(FX)

#include "fxdrv.h"
#include "enums.h"

/**********************************************************************/
/*****                 Miscellaneous functions                    *****/
/**********************************************************************/

/* Enalbe/Disable dithering */
void fxDDDither(GLcontext *ctx, GLboolean enable)
{
  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDDither()\n");
  }

  if(enable)
    grDitherMode(GR_DITHER_4x4);
  else
    grDitherMode(GR_DITHER_DISABLE);
}


/* Return buffer size information */
void fxDDBufferSize(GLcontext *ctx, GLuint *width, GLuint *height)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDBufferSize(...) Start\n");
  }

  *width=fxMesa->width;
  *height=fxMesa->height;

  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDBufferSize(...) End\n");
  }
}


/* Set current drawing color */
static void fxDDSetColor(GLcontext *ctx, GLubyte red, GLubyte green,
                         GLubyte blue, GLubyte alpha )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLubyte col[4];
  ASSIGN_4V( col, red, green, blue, alpha );
  
  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDSetColor(%d,%d,%d,%d)\n",red,green,blue,alpha);
  }

  fxMesa->color=FXCOLOR4(col);
}


/* Implements glClearColor() */
static void fxDDClearColor(GLcontext *ctx, GLubyte red, GLubyte green,
                           GLubyte blue, GLubyte alpha )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLubyte col[4];



  ASSIGN_4V( col, red, green, blue, 255 );

  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDClearColor(%d,%d,%d,%d)\n",red,green,blue,alpha);
  }
 
  fxMesa->clearC=FXCOLOR4( col );
  fxMesa->clearA=alpha;
}

/* Clear the color and/or depth buffers */
static GLbitfield fxDDClear(GLcontext *ctx, GLbitfield mask, GLboolean all,
                            GLint x, GLint y, GLint width, GLint height )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLbitfield newmask;

  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDClear(%d,%d,%d,%d)\n",x,y,width,height);
  }

  switch(mask & (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)) {
  case (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT):
    /* clear color and depth buffer */

    if (ctx->Color.DrawDestMask & BACK_LEFT_BIT) {
      grRenderBuffer(GR_BUFFER_BACKBUFFER);
      grBufferClear(fxMesa->clearC, fxMesa->clearA,
                    (FxU16)(ctx->Depth.Clear*0xffff));
    }
    if (ctx->Color.DrawDestMask & FRONT_LEFT_BIT) {
       grRenderBuffer(GR_BUFFER_FRONTBUFFER);
       grBufferClear(fxMesa->clearC, fxMesa->clearA,
                     (FxU16)(ctx->Depth.Clear*0xffff));
    }

    newmask=mask & (~(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT));
    break;
  case (GL_COLOR_BUFFER_BIT):
    /* clear color buffer */

    if(ctx->Color.ColorMask) {
      grDepthMask(FXFALSE);

      if (ctx->Color.DrawDestMask & BACK_LEFT_BIT) {
        grRenderBuffer(GR_BUFFER_BACKBUFFER);
        grBufferClear(fxMesa->clearC, fxMesa->clearA, 0);
      }
      if (ctx->Color.DrawDestMask & FRONT_LEFT_BIT) {
        grRenderBuffer(GR_BUFFER_FRONTBUFFER);
        grBufferClear(fxMesa->clearC, fxMesa->clearA, 0);
      }

      if(ctx->Depth.Mask)
        grDepthMask(FXTRUE);
    }

    newmask=mask & (~(GL_COLOR_BUFFER_BIT));
    break;
  case (GL_DEPTH_BUFFER_BIT):
    /* clear depth buffer */

    if(ctx->Depth.Mask) {
      grColorMask(FXFALSE,FXFALSE);
      grBufferClear(fxMesa->clearC, fxMesa->clearA,
                    (FxU16)(ctx->Depth.Clear*0xffff));

      grColorMask(ctx->Color.ColorMask[RCOMP] ||
                  ctx->Color.ColorMask[GCOMP] ||
                  ctx->Color.ColorMask[BCOMP],
                  ctx->Color.ColorMask[ACOMP] && fxMesa->haveAlphaBuffer);
    }

    newmask=mask & (~(GL_DEPTH_BUFFER_BIT));
    break;
  default:
    newmask=mask;
    break;
  }
   
  return newmask;
}


/*  Set the buffer used in double buffering */
static GLboolean fxDDSetBuffer(GLcontext *ctx, GLenum mode )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxDDSetBuffer(%x)\n",mode);
  }

  if (mode == GL_FRONT_LEFT) {
    fxMesa->currentFB = GR_BUFFER_FRONTBUFFER;
    grRenderBuffer(fxMesa->currentFB);
    return GL_TRUE;
  }
  else if (mode == GL_BACK_LEFT) {
    fxMesa->currentFB = GR_BUFFER_BACKBUFFER;
    grRenderBuffer(fxMesa->currentFB);
    return GL_TRUE;
  }
  else {
    return GL_FALSE;
  }
}


static GLboolean fxDDDrawBitMap(GLcontext *ctx, GLint px, GLint py,
                                GLsizei width, GLsizei height,
                                const struct gl_pixelstore_attrib *unpack,
                                const GLubyte *bitmap)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  FxU16 *p;
  GrLfbInfo_t info;
  const GLubyte *pb;
  int x,y;
  GLint r,g,b,a,scrwidth,scrheight,stride;
  FxU16 color;

  /* TODO: with a little work, these bitmap unpacking parameter restrictions
   * could be removed.
   */
  if((unpack->Alignment!=1) ||
     (unpack->RowLength!=0) ||
     (unpack->SkipPixels!=0) ||
     (unpack->SkipRows!=0) ||
     (unpack->SwapBytes) ||
     (unpack->LsbFirst))
    return GL_FALSE;

#define ISCLIPPED(rx) ( ((rx)<0) || ((rx)>=scrwidth) )
#define DRAWBIT(i) {	   \
  if(!ISCLIPPED(x+px))	   \
    if( (*pb) & (1<<(i)) ) \
      (*p)=color;	   \
  p++;                     \
  x++;			   \
  if(x>=width) {           \
    pb++;                  \
    break;                 \
  }                        \
}

  scrwidth=fxMesa->width;
  scrheight=fxMesa->height;

  if((px>=scrwidth) || (px+width<=0) || (py>=scrheight) || (py+height<=0))
    return GL_TRUE;

  pb=bitmap;

  if(py<0) {
    pb+=(height*(-py)) >> (3+1);
    height+=py;
    py=0;
  }

  if(py+height>=scrheight)
    height-=(py+height)-scrheight;

  info.size=sizeof(info);
  if(!grLfbLock(GR_LFB_WRITE_ONLY,
                fxMesa->currentFB,
                GR_LFBWRITEMODE_565,
                GR_ORIGIN_UPPER_LEFT,
                FXFALSE,
                &info)) {
#ifndef FX_SILENT
    fprintf(stderr,"fx Driver: error locking the linear frame buffer\n");
#endif
    return GL_TRUE;
  }

  r=(GLint)(ctx->Current.RasterColor[0]*255.0f);
  g=(GLint)(ctx->Current.RasterColor[1]*255.0f);
  b=(GLint)(ctx->Current.RasterColor[2]*255.0f);
  a=(GLint)(ctx->Current.RasterColor[3]*255.0f);
  color=(FxU16)
    ( ((FxU16)0xf8 & b) <<(11-3))  |
    ( ((FxU16)0xfc & g) <<(5-3+1)) |
    ( ((FxU16)0xf8 & r) >> 3);

  stride=info.strideInBytes>>1;

  /* This code is a bit slow... */

  for(y=0;y<height;y++) {
    p=((FxU16 *)info.lfbPtr)+px+((scrheight-(y+py))*stride);

    for(x=0;;) {
      DRAWBIT(7);       DRAWBIT(6);     DRAWBIT(5);     DRAWBIT(4);
      DRAWBIT(3);       DRAWBIT(2);     DRAWBIT(1);     DRAWBIT(0);
      pb++;
    }
  }

  grLfbUnlock(GR_LFB_WRITE_ONLY,fxMesa->currentFB);

#undef ISCLIPPED
#undef DRAWBIT

  return GL_TRUE;
}

static void fxDDFinish(GLcontext *ctx)
{
  FX_grFlush();
}


static GLint fxDDGetParameteri(const GLcontext *ctx, GLint param)
{
  switch(param) {
  case DD_HAVE_HARDWARE_FOG:
    return 1;
  default:
    fprintf(stderr,"fx Driver: internal error in fxDDGetParameteri(): %x\n",param);
    fxCloseHardware();
    exit(-1);
  }
}


void fxDDSetNearFar(GLcontext *ctx, GLfloat n, GLfloat f)
{
   FX_CONTEXT(ctx)->new_state |= FX_NEW_FOG;
   ctx->Driver.RenderStart = fxSetupFXUnits;
}

/* KW: Put the word Mesa in the render string because quakeworld
 * checks for this rather than doing a glGet(GL_MAX_TEXTURE_SIZE).
 * Why?
 */
static const GLubyte *fxDDGetString(GLcontext *ctx, GLenum name)
{
  static char *extensions="GL_EXT_blend_color GL_EXT_blend_minmax GL_EXT_blend_logic_op GL_EXT_blend_subtract GL_EXT_paletted_texture GL_EXT_point_parameters GL_EXT_polygon_offset GL_EXT_vertex_array GL_EXT_texture_object GL_EXT_texture3D GL_MESA_window_pos GL_MESA_resize_buffers GL_EXT_shared_texture_palette GL_EXT_rescale_normal GL_EXT_abgr GL_SGIS_texture_edge_clamp 3DFX_set_global_palette GL_FXMESA_global_texture_lod_bias";

  static char buf[MAX_NUM_SST][64];

  fxQueryHardware();

  switch (name) {
    case GL_RENDERER:
      if(glbHWConfig.SSTs[glbCurrentBoard].type==GR_SSTTYPE_VOODOO) {
        sprintf(buf[glbCurrentBoard],"Mesa Glide v0.30 Voodoo_Graphics %d CARD/%d FB/%d TM/%d TMU/%s",
                glbCurrentBoard,

                (glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.sliDetect ?
                 (glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.fbRam*2) :
                 glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.fbRam),

                glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.tmuConfig[GR_TMU0].tmuRam+
                ((glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.nTexelfx>1) ?
                 glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.tmuConfig[GR_TMU1].tmuRam :
                 0),

                glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.nTexelfx,

                (glbHWConfig.SSTs[glbCurrentBoard].sstBoard.VoodooConfig.sliDetect ? "SLI" : "NOSLI")
                );
      }
      else {
        if(glbHWConfig.SSTs[glbCurrentBoard].type==GR_SSTTYPE_SST96)
          sprintf(buf[glbCurrentBoard],"Glide v0.30 Voodoo_Rush %d CARD/%d FB/%d TM/%d TMU/NOSLI",
                  glbCurrentBoard,

                  glbHWConfig.SSTs[glbCurrentBoard].sstBoard.SST96Config.fbRam,

                  glbHWConfig.SSTs[glbCurrentBoard].sstBoard.SST96Config.tmuConfig.tmuRam,

                  glbHWConfig.SSTs[glbCurrentBoard].sstBoard.SST96Config.nTexelfx	      
                  );
        else
          strcpy(buf[glbCurrentBoard],"Glide v0.30 UNKNOWN");
      }
      return (GLubyte *) buf[glbCurrentBoard];
    case GL_EXTENSIONS:
      return (GLubyte *) extensions;
    default:
      return NULL;
  }
}


void fxDDInitExtensions( GLcontext *ctx )
{
   fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

   gl_extensions_add( ctx, DEFAULT_ON, "3DFX_set_global_palette", 0 );
   gl_extensions_add( ctx, DEFAULT_ON, "GL_FXMESA_global_texture_lod_bias", 0);
   
   if (!fxMesa->emulateTwoTMUs) 
      gl_extensions_disable( ctx, "GL_ARB_multitexture" );
}

/************************************************************************/
/************************************************************************/
/************************************************************************/

/* This is a no-op, since the z-buffer is in hardware */
static void fxAllocDepthBuffer(GLcontext *ctx)
{
   if (MESA_VERBOSE&VERBOSE_DRIVER) {
     fprintf(stderr,"fxmesa: fxAllocDepthBuffer()\n");
   }
}

/************************************************************************/
/************************************************************************/
/************************************************************************/

/* Check if the hardware supports the current context 
 *
 * Performs similar work to fxDDChooseRenderState() - should be merged.
 */
static GLboolean fxIsInHardware(GLcontext *ctx)
{
   fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

  if (!ctx->Hint.AllowDrawMem)
     return GL_TRUE;		/* you'll take it and like it */

  if((ctx->RasterMask & STENCIL_BIT) ||
     ((ctx->Color.BlendEnabled) && (ctx->Color.BlendEquation!=GL_FUNC_ADD_EXT)) ||
     ((ctx->Color.ColorLogicOpEnabled) && (ctx->Color.LogicOp!=GL_COPY)) ||
     (ctx->Light.Model.ColorControl==GL_SEPARATE_SPECULAR_COLOR) ||
     (!((ctx->Color.ColorMask[RCOMP]==ctx->Color.ColorMask[GCOMP]) &&
        (ctx->Color.ColorMask[GCOMP]==ctx->Color.ColorMask[BCOMP]) &&
        (ctx->Color.ColorMask[ACOMP]==ctx->Color.ColorMask[ACOMP])))
     )
    return GL_FALSE;

  /* Unsupported texture/multitexture cases */

  if(fxMesa->emulateTwoTMUs) {
    if((ctx->Enabled & (TEXTURE0_3D | TEXTURE1_3D)) ||
       /* Not very well written ... */
       ((ctx->Enabled & (TEXTURE0_1D | TEXTURE1_1D)) && 
        ((ctx->Enabled & (TEXTURE0_2D | TEXTURE1_2D))!=(TEXTURE0_2D | TEXTURE1_2D)))
       )
      return GL_FALSE;

    if((ctx->Texture.ReallyEnabled & TEXTURE0_2D) &&
       (ctx->Texture.Unit[0].EnvMode==GL_BLEND))
      return GL_FALSE;

    if((ctx->Texture.ReallyEnabled & TEXTURE1_2D) &&
       (ctx->Texture.Unit[1].EnvMode==GL_BLEND))
      return GL_FALSE;


    if (MESA_VERBOSE & (VERBOSE_DRIVER|VERBOSE_TEXTURE))
       fprintf(stderr, "fxMesa: fxIsInHardware, envmode is %s/%s\n",
	       gl_lookup_enum_by_nr(ctx->Texture.Unit[0].EnvMode),
	       gl_lookup_enum_by_nr(ctx->Texture.Unit[1].EnvMode));

    /* KW: This was wrong (I think) and I changed it... which doesn't mean
     * it is now correct...
     */
    if((ctx->Enabled & (TEXTURE0_1D | TEXTURE0_2D | TEXTURE0_3D)) &&
       (ctx->Enabled & (TEXTURE1_1D | TEXTURE1_2D | TEXTURE1_3D)))
    {
       /* Can't use multipass to blend a multitextured triangle - fall
	* back to software.
	*/
       if (!fxMesa->haveTwoTMUs && ctx->Color.BlendEnabled) 
	  return GL_FALSE;
	  
       if ((ctx->Texture.Unit[0].EnvMode!=ctx->Texture.Unit[1].EnvMode) &&
	   (ctx->Texture.Unit[0].EnvMode!=GL_MODULATE) &&
	   (ctx->Texture.Unit[0].EnvMode!=GL_REPLACE)) /* q2, seems ok... */
       {
	  if (MESA_VERBOSE&VERBOSE_DRIVER)
	    fprintf(stderr, "fxMesa: unsupported multitex env mode\n");

	  return GL_FALSE;
       }
    }
  } else {
    if((ctx->Enabled & (TEXTURE1_1D | TEXTURE1_2D | TEXTURE1_3D)) ||
       /* Not very well written ... */
       ((ctx->Enabled & TEXTURE0_1D) && 
        (!(ctx->Enabled & TEXTURE0_2D)))
       )
      return GL_FALSE;

    
    if((ctx->Texture.ReallyEnabled & TEXTURE0_2D) &&
       (ctx->Texture.Unit[0].EnvMode==GL_BLEND))
      return GL_FALSE;
  }

  return GL_TRUE;
}



#define INTERESTED (~(NEW_MODELVIEW|NEW_PROJECTION|NEW_PROJECTION|NEW_TEXTURE_MATRIX|NEW_USER_CLIP|NEW_CLIENT_STATE|NEW_TEXTURE_ENABLE))

static void fxDDUpdateDDPointers(GLcontext *ctx)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLuint new_state = ctx->NewState;

  if (MESA_VERBOSE&(VERBOSE_DRIVER|VERBOSE_STATE)) 
    fprintf(stderr,"fxmesa: fxDDUpdateDDPointers(...)\n");

  if (new_state & (NEW_RASTER_OPS|NEW_TEXTURING))
     fxMesa->is_in_hardware = fxIsInHardware(ctx);

  if (fxMesa->is_in_hardware) {
    if (fxMesa->new_state)
      fxSetupFXUnits(ctx);

    if(new_state & INTERESTED) {
      fxDDChooseRenderState( ctx );
      fxMesa->RenderVBTables=fxDDChooseRenderVBTables(ctx);
      fxMesa->RenderVBClippedTab=fxMesa->RenderVBTables[0];
      fxMesa->RenderVBCulledTab=fxMesa->RenderVBTables[1];
      fxMesa->RenderVBRawTab=fxMesa->RenderVBTables[2];

      ctx->Driver.RasterSetup=fxDDChooseSetupFunction(ctx);
    }
      
    ctx->Driver.PointsFunc=fxMesa->PointsFunc;
    ctx->Driver.LineFunc=fxMesa->LineFunc;
    ctx->Driver.TriangleFunc=fxMesa->TriangleFunc;
    ctx->Driver.QuadFunc=fxMesa->QuadFunc;
  }

  ctx->Driver.AllocDepthBuffer=fxAllocDepthBuffer;
  ctx->Driver.DepthTestSpan=fxDDDepthTestSpanGeneric;
  ctx->Driver.DepthTestPixels=fxDDDepthTestPixelsGeneric;
  ctx->Driver.ReadDepthSpanFloat=fxDDReadDepthSpanFloat;
  ctx->Driver.ReadDepthSpanInt=fxDDReadDepthSpanInt;
  ctx->Driver.RenderStart = 0;
}


void fxSetupDDPointers(GLcontext *ctx)
{
  if (MESA_VERBOSE&VERBOSE_DRIVER) {
    fprintf(stderr,"fxmesa: fxSetupDDPointers()\n");
  }

  ctx->Driver.UpdateState=fxDDUpdateDDPointers;
         
  ctx->Driver.GetString=fxDDGetString;

  ctx->Driver.Dither=fxDDDither;

  ctx->Driver.NearFar=fxDDSetNearFar;

  ctx->Driver.GetParameteri=fxDDGetParameteri;

  ctx->Driver.ClearIndex=NULL;
  ctx->Driver.ClearColor=fxDDClearColor;
  ctx->Driver.Clear=fxDDClear;

  ctx->Driver.Index=NULL;
  ctx->Driver.Color=fxDDSetColor;

  ctx->Driver.SetBuffer=fxDDSetBuffer;
  ctx->Driver.GetBufferSize=fxDDBufferSize;

  ctx->Driver.Bitmap=fxDDDrawBitMap;
  ctx->Driver.DrawPixels=NULL;

  ctx->Driver.Finish=fxDDFinish;
  ctx->Driver.Flush=NULL;

  ctx->Driver.RenderStart=NULL;
  ctx->Driver.RenderFinish=NULL;

  ctx->Driver.TexEnv=fxDDTexEnv;
  ctx->Driver.TexImage=fxDDTexImg;
  ctx->Driver.TexSubImage=fxDDTexSubImg;
  ctx->Driver.TexParameter=fxDDTexParam;
  ctx->Driver.BindTexture=fxDDTexBind;
  ctx->Driver.DeleteTexture=fxDDTexDel;
  ctx->Driver.UpdateTexturePalette=fxDDTexPalette;
  ctx->Driver.UseGlobalTexturePalette=fxDDTexUseGlbPalette;

  ctx->Driver.RectFunc=NULL;

  ctx->Driver.AlphaFunc=fxDDAlphaFunc;
  ctx->Driver.BlendFunc=fxDDBlendFunc;
  ctx->Driver.DepthFunc=fxDDDepthFunc;
  ctx->Driver.DepthMask=fxDDDepthMask;
  ctx->Driver.ColorMask=fxDDColorMask;
  ctx->Driver.Fogfv=fxDDFogfv;
  ctx->Driver.Scissor=fxDDScissor;
  ctx->Driver.FrontFace=fxDDFrontFace;
  ctx->Driver.CullFace=fxDDCullFace;
  ctx->Driver.ShadeModel=fxDDShadeModel;
  ctx->Driver.Enable=fxDDEnable;
  

  ctx->Driver.RegisterVB=fxDDRegisterVB;
  ctx->Driver.UnregisterVB=fxDDUnregisterVB;

  ctx->Driver.RegisterPipelineStages = fxDDRegisterPipelineStages;

  ctx->Driver.OptimizeImmediatePipeline = 0; /* nothing done yet */
  ctx->Driver.OptimizePrecalcPipeline = 0;

/*    if (getenv("MESA_USE_FAST") || getenv("FX_USE_FAST")) */
/*       ctx->Driver.OptimizePrecalcPipeline = fxDDOptimizePrecalcPipeline; */

  if (!getenv("FX_NO_FAST")) 
      ctx->Driver.BuildPrecalcPipeline = fxDDBuildPrecalcPipeline; 

  ctx->Driver.TriangleCaps = DD_TRI_CULL|DD_TRI_OFFSET|DD_TRI_LIGHT_TWOSIDE;

  fxSetupDDSpanPointers(ctx);

  FX_CONTEXT(ctx)->render_index = 1; /* force an update */
  fxDDUpdateDDPointers(ctx);
}


#else


/*
 * Need this to provide at least one external definition.
 */

int gl_fx_dummy_function_dd(void)
{
  return 0;
}

#endif  /* FX */
