/* draw.c */

/*
 * Mesa 3-D graphics library
 * Version:  1.2
 * Copyright (C) 1995  Brian Paul  (brianp@ssec.wisc.edu)
 *
 * 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.
 */


/*
$Id: draw.c,v 1.40 1995/11/22 13:36:18 brianp Exp $

$Log: draw.c,v $
 * Revision 1.40  1995/11/22  13:36:18  brianp
 * small optimization to VB.Win[][] computation
 *
 * Revision 1.39  1995/11/03  22:35:08  brianp
 * call new vertex fogging functions
 *
 * Revision 1.38  1995/11/03  17:40:52  brianp
 * removed unused variables
 *
 * Revision 1.37  1995/11/01  21:45:26  brianp
 * use new gl_color_shade_vertices() function
 *
 * Revision 1.36  1995/10/27  21:37:44  brianp
 * implemented glPolygonOffsetEXT
 * optimized computation of CC.Plane[ABCD]
 *
 * Revision 1.35  1995/10/19  15:47:00  brianp
 * added gamma support
 *
 * Revision 1.34  1995/10/17  21:42:55  brianp
 * enabled VB.MonoColor logic
 *
 * Revision 1.33  1995/09/28  19:40:02  brianp
 * replaced ClipFlag[] with Unclipped[]
 *
 * Revision 1.32  1995/09/27  18:33:24  brianp
 * added call to new gl_transform_normals function
 *
 * Revision 1.31  1995/09/26  16:04:55  brianp
 * moved transform_points to xform.c
 *
 * Revision 1.30  1995/09/25  19:23:40  brianp
 * implemented per-vertex glMaterial calls
 *
 * Revision 1.29  1995/09/22  22:12:41  brianp
 * optimized the xform_vb function to unroll loops, use maximum registers
 *
 * Revision 1.28  1995/09/22  16:51:16  brianp
 * fixed Anyclipped typo
 *
 * Revision 1.27  1995/09/22  16:49:42  brianp
 * added VB.AnyClipped logic
 * added conditionals to gl_execute_vertex
 * prototyped VB.MonoColor logic
 *
 * Revision 1.26  1995/09/17  19:31:03  brianp
 * better logic for computing plane eq of polygons, accidental culling fixed
 *
 * Revision 1.25  1995/09/15  18:39:43  brianp
 * complete restructuring to "vectorize" the vertex pipeline
 *
 * Revision 1.24  1995/07/28  21:33:21  brianp
 * code cleanup, adapt gl_index_shade calls for GLfloat result
 *
 * Revision 1.23  1995/06/20  16:29:36  brianp
 * don't scale Z to integer values here
 * introduced new triangle, polygon area code but not used yet
 *
 * Revision 1.22  1995/06/02  18:59:31  brianp
 * added special case for computing plane equation of 3-sided polygon
 *
 * Revision 1.21  1995/06/02  13:55:09  brianp
 * implemented vertex/primitive buffering
 *
 * Revision 1.20  1995/05/26  19:32:02  brianp
 * replace many assignments in gl_color() with macros
 *
 * Revision 1.19  1995/05/22  21:02:41  brianp
 * Release 1.2
 *
 * Revision 1.18  1995/05/22  21:00:36  brianp
 * tried initial vertex buffering
 *
 * Revision 1.17  1995/05/17  13:17:22  brianp
 * changed default CC.Mode value to allow use of real OpenGL headers
 * removed need for CC.MajorMode variable
 *
 * Revision 1.16  1995/05/15  16:07:33  brianp
 * moved StippleCounter init to new place
 *
 * Revision 1.15  1995/05/12  16:29:18  brianp
 * added #define for NULL
 *
 * Revision 1.14  1995/03/27  20:31:26  brianp
 * new Texture.Enabled scheme
 *
 * Revision 1.13  1995/03/24  15:31:23  brianp
 * introduced VB
 *
 * Revision 1.12  1995/03/23  17:10:12  brianp
 * changed render_point() to render_points(n)
 *
 * Revision 1.11  1995/03/09  21:42:09  brianp
 * new ModelViewInv matrix logic
 *
 * Revision 1.10  1995/03/09  20:08:04  brianp
 * introduced TRANSFORM_POINT and TRANSFORM_NORMAL macros
 *
 * Revision 1.9  1995/03/04  19:29:44  brianp
 * 1.1 beta revision
 *
 * Revision 1.8  1995/03/02  19:10:00  brianp
 * fixed a texgen bug
 *
 * Revision 1.7  1995/02/27  22:48:48  brianp
 * modified for PB
 *
 * Revision 1.6  1995/02/27  15:08:04  brianp
 * added Vcolor/Vindex scheme
 *
 * Revision 1.5  1995/02/26  22:58:43  brianp
 * more zero-area polygon work
 *
 * Revision 1.4  1995/02/26  21:59:43  brianp
 * *** empty log message ***
 *
 * Revision 1.3  1995/02/25  22:07:56  brianp
 * relaxed test for zero-area polygons, still not perfect though
 *
 * Revision 1.2  1995/02/24  15:23:36  brianp
 * removed initialization of d from render_point()
 * added RasterColor code to gl_rasterpos()
 *
 * Revision 1.1  1995/02/24  14:20:43  brianp
 * Initial revision
 *
 */


/*
 * Draw points, lines, and polygons.
 */


#ifdef DEBUG
#  include <assert.h>
#endif
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "clip.h"
#include "context.h"
#include "dd.h"
#include "draw.h"
#include "feedback.h"
#include "fog.h"
#include "gamma.h"
#include "light.h"
#include "lines.h"
#include "list.h"
#include "macros.h"
#include "pb.h"
#include "points.h"
#include "polygons.h"
#include "texture.h"
#include "vb.h"
#include "xform.h"

/*#include "triangle.h"*/



#ifndef NULL
#  define NULL 0
#endif


#ifdef DEBUG
#  define ASSERT(X)   assert(X)
#else
#  define ASSERT(X)
#endif



/*
 * Check if the global material has to be updated with info that was
 * associated with a vertex via glMaterial.
 */
static void update_material( GLuint i )
{
   if (VB.MaterialMask[i]) {
      if (VB.MaterialMask[i] & FRONT_AMBIENT_BIT) {
         COPY_4V( CC.Light.Material[0].Ambient, VB.Material[i][0].Ambient );
      }
      if (VB.MaterialMask[i] & BACK_AMBIENT_BIT) {
         COPY_4V( CC.Light.Material[1].Ambient, VB.Material[i][1].Ambient );
      }
      if (VB.MaterialMask[i] & FRONT_DIFFUSE_BIT) {
         COPY_4V( CC.Light.Material[0].Diffuse, VB.Material[i][0].Diffuse );
      }
      if (VB.MaterialMask[i] & BACK_DIFFUSE_BIT) {
         COPY_4V( CC.Light.Material[1].Diffuse, VB.Material[i][1].Diffuse );
      }
      if (VB.MaterialMask[i] & FRONT_SPECULAR_BIT) {
         COPY_4V( CC.Light.Material[0].Specular, VB.Material[i][0].Specular );
      }
      if (VB.MaterialMask[i] & BACK_SPECULAR_BIT) {
         COPY_4V( CC.Light.Material[1].Specular, VB.Material[i][1].Specular );
      }
      if (VB.MaterialMask[i] & FRONT_EMISSION_BIT) {
         COPY_4V( CC.Light.Material[0].Emission, VB.Material[i][0].Emission );
      }
      if (VB.MaterialMask[i] & BACK_EMISSION_BIT) {
         COPY_4V( CC.Light.Material[1].Emission, VB.Material[i][1].Emission );
      }
      if (VB.MaterialMask[i] & FRONT_SHININESS_BIT) {
         CC.Light.Material[0].Shininess = VB.Material[i][0].Shininess;
      }
      if (VB.MaterialMask[i] & BACK_SHININESS_BIT) {
         CC.Light.Material[1].Shininess = VB.Material[i][1].Shininess;
      }
      if (VB.MaterialMask[i] & FRONT_INDEXES_BIT) {
         CC.Light.Material[0].AmbientIndex = VB.Material[i][0].AmbientIndex;
         CC.Light.Material[0].DiffuseIndex = VB.Material[i][0].DiffuseIndex;
         CC.Light.Material[0].SpecularIndex = VB.Material[i][0].SpecularIndex;
      }
      if (VB.MaterialMask[i] & BACK_INDEXES_BIT) {
         CC.Light.Material[1].AmbientIndex = VB.Material[i][1].AmbientIndex;
         CC.Light.Material[1].DiffuseIndex = VB.Material[i][1].DiffuseIndex;
         CC.Light.Material[1].SpecularIndex = VB.Material[i][1].SpecularIndex;
      }
      VB.MaterialMask[i] = 0;  /* reset now */
   }
}



/*
 * Render a line segment from VB[v1] to VB[v2] when either one or both
 * endpoints must be clipped.
 */
static void render_clipped_line( GLuint v1, GLuint v2 )
{
   GLfloat d;
   GLfloat ndc_x, ndc_y, ndc_z;
   GLuint provoking_vertex;

   /* which vertex dictates the color when flat shading: */
   provoking_vertex = v2;

   /*
    * Clipping may introduce new vertices.  New vertices will be stored
    * in the vertex buffer arrays starting with location VB.Free.  After
    * we've rendered the line, these extra vertices can be overwritten.
    */
   VB.Free = VB_MAX;

   /* Clip against user clipping planes */
   if (CC.Transform.AnyClip) {
      if (gl_userclip_line( &v1, &v2 )==0)
	return;
   }

   /* Apply projection matrix:  clip = Proj * eye */
   TRANSFORM_POINT( VB.Clip[v1], CC.ProjectionMatrix, VB.Eye[v1] );
   TRANSFORM_POINT( VB.Clip[v2], CC.ProjectionMatrix, VB.Eye[v2] );

   /* Clip against view volume */
   if (gl_viewclip_line( &v1, &v2 )==0)
      return;

   /* Transform from clip coords to ndc:  ndc = clip / W */
   ASSERT( VB.Clip[v1][3] != 0.0 );
   ASSERT( VB.Clip[v2][3] != 0.0 );
   d = 1.0F / VB.Clip[v1][3];
   ndc_x = VB.Clip[v1][0] * d;
   ndc_y = VB.Clip[v1][1] * d;
   ndc_z = VB.Clip[v1][2] * d;

   /* Map ndc coord to window coords. */
   VB.Win[v1][0] = MAP_X( ndc_x );
   VB.Win[v1][1] = MAP_Y( ndc_y );
   VB.Win[v1][2] = MAP_Z( ndc_z );

   /* Transform from clip coords to ndc:  ndc = clip / W */
   d = 1.0F / VB.Clip[v2][3];
   ndc_x = VB.Clip[v2][0] * d;
   ndc_y = VB.Clip[v2][1] * d;
   ndc_z = VB.Clip[v2][2] * d;

   /* Map ndc coord to window coords. */
   VB.Win[v2][0] = MAP_X( ndc_x );
   VB.Win[v2][1] = MAP_Y( ndc_y );
   VB.Win[v2][2] = MAP_Z( ndc_z );

   (*CC.LineFunc)( v1, v2, provoking_vertex );
}



#ifdef LEAVEOUT
/*
 * Apply the glPolygonOffsetEXT transformation to the current plane
 * equation.  Basically, we just have to add an offset to the D term.
 */
static void offset_polygon( void )
{
   GLfloat ac, bc, m, offset;
   ac = CC.PlaneA / CC.PlaneC;
   bc = CC.PlaneB / CC.PlaneC;
   if (ac<0.0F)  ac = -ac;
   if (bc<0.0F)  bc = -bc;
   m = MAX2( ac, bc );
/*
   m = sqrt( ac*ac + bc*bc );
*/
   offset = m * CC.Polygon.OffsetFactor + CC.Polygon.OffsetBias;
   CC.PlaneD = CC.PlaneD + CC.PlaneC * offset;
}
#endif


#define OFFSET_POLYGON( A, B, C, D )					\
	{								\
	   GLfloat m, ac = (A)/(C), bc = (B)/(C);			\
	   if (ac<0.0F)  ac = -ac;					\
	   if (bc<0.0F)  bc = -bc;					\
	   m = MAX2( ac, bc );						\
	   D += (C) * (m * CC.Polygon.OffsetFactor + CC.Polygon.OffsetBias); \
	}




/*
 * Render a polygon in which at least one vertex has to be clipped.
 * Input:  n - number of vertices
 *         vlist - list of vertices in the polygon.
 *         odd_flag - if non-zero, reverse the orientation of the polygon
 */
static void render_clipped_polygon( GLuint n, GLuint vlist[], GLuint odd_flag )
{
   GLuint i, j;
   GLuint provoking_vertex;
   GLuint facing;

   /* which vertex dictates the color when flat shading: */
   provoking_vertex = (CC.Mode==GL_POLYGON) ? vlist[0] : vlist[n-1];

   /*
    * Clipping may introduce new vertices.  New vertices will be stored
    * in the vertex buffer arrays starting with location VB.Free.  After
    * we've rendered the polygon, these extra vertices can be overwritten.
    */
   VB.Free = VB_MAX;

   /* Clip against user clipping planes in eye coord space. */
   if (CC.Transform.AnyClip) {
      n = gl_userclip_polygon( n, vlist );
      if (n<3)
	return;
   }

   /* Transform vertices from eye to clip coordinates:  clip = Proj * eye */
   for (i=0;i<n;i++) {
      j = vlist[i];
      TRANSFORM_POINT( VB.Clip[j], CC.ProjectionMatrix, VB.Eye[j] );
   }

   /* Clip against view volume in clip coord space */
   n = gl_viewclip_polygon( n, vlist );
   if (n<3)
     return;

   /* Transform vertices from clip to ndc:  ndc = clip / W */
   for (i=0;i<n;i++) {
      GLfloat d, ndc_x, ndc_y, ndc_z;
      j = vlist[i];
      ASSERT( VB.Clip[j][3] != 0.0 );
      d = 1.0F / VB.Clip[j][3];
      ndc_x = VB.Clip[j][0] * d;
      ndc_y = VB.Clip[j][1] * d;
      ndc_z = VB.Clip[j][2] * d;

      /* Transform ndc coord to a window coord.  Note that window Z values */
      /* are scaled to integer Z buffer values. */
      VB.Win[j][0] = MAP_X( ndc_x );
      VB.Win[j][1] = MAP_Y( ndc_y );
      VB.Win[j][2] = MAP_Z( ndc_z );

#ifdef NEW
      VB.WinX[j] = (GLint) (MAP_X( ndc_x ) * SUB_PIX_SCALE);
      VB.WinY[j] = (GLint) (MAP_Y( ndc_y ) * SUB_PIX_SCALE);
      VB.WinZ[j] = (GLint) (MAP_Z( ndc_z ) * DEPTH_SCALE);
#endif
   }

   /* Compute the plane equation of polygon: ax + by + cz = d */
   if (CC.ComputePlane) {
      GLuint j0 = vlist[0];
      GLuint j1 = vlist[1];
      GLuint j2 = vlist[2];
      GLuint j3 = vlist[ (n==3) ? 0 : 3 ];
      GLfloat ex = VB.Win[j1][0] - VB.Win[j3][0];
      GLfloat ey = VB.Win[j1][1] - VB.Win[j3][1];
      GLfloat ez = VB.Win[j1][2] - VB.Win[j3][2];
      GLfloat fx = VB.Win[j2][0] - VB.Win[j0][0];
      GLfloat fy = VB.Win[j2][1] - VB.Win[j0][1];
      GLfloat fz = VB.Win[j2][2] - VB.Win[j0][2];
      GLfloat a, b, c, d;
      c = ex*fy-ey*fx;
      if (c==0.0F) {
         /* polygon is perpindicular to view plane, don't draw it */
         return;
      }

      /* compute orientation:  0=front, 1=back */
      facing = (c<0.0F) ^ odd_flag ^ (CC.Polygon.FrontFace==GL_CW);

      if (CC.Polygon.CullFlag && facing==(CC.Polygon.CullFaceMode==GL_BACK)) {
	 return;   /* culled */
      }
      a = ey*fz-ez*fy;
      b = ez*fx-ex*fz;
      d = a*VB.Win[j0][0] + b*VB.Win[j0][1] + c*VB.Win[j0][2];
      if (CC.Polygon.OffsetEnabled) {
         OFFSET_POLYGON( a, b, c, d );
      }
      CC.PlaneA = a;
      CC.PlaneB = b;
      CC.PlaneC = c;
      CC.PlaneD = d;
   }
   else {
      CC.PlaneA = 0.0F;
      CC.PlaneB = 0.0F;
      CC.PlaneC = 1.0F;
      CC.PlaneD = 0.0F;
      facing = 0;
   }

   if (facing==1 && CC.Light.Enabled && CC.Light.Model.TwoSide) {
      /* use back color or index */
      VB.Color = VB.Bcolor;
      VB.Index = VB.Bindex;
   }
   else {
      /* use front color or index */
      VB.Color = VB.Fcolor;
      VB.Index = VB.Findex;
   }

   (*CC.PolygonFunc)( n, vlist, provoking_vertex );
}



/*
 * Render a polygon in which doesn't have to be clipped.
 * Input:  n - number of vertices
 *         vlist - list of vertices in the polygon.
 *         odd_flag - if non-zero, reverse the orientation of the polygon
 */
static void render_polygon( GLuint n, GLuint vlist[], GLuint odd_flag )
{
   GLuint provoking_vertex;
   GLuint facing;

   /* which vertex dictates the color when flat shading: */
   provoking_vertex = (CC.Mode==GL_POLYGON) ? vlist[0] : vlist[n-1];

   /* Compute the plane equation of polygon: ax + by + cz = d */
   if (CC.ComputePlane) {
      GLuint j0 = vlist[0];
      GLuint j1 = vlist[1];
      GLuint j2 = vlist[2];
      GLuint j3 = vlist[ (n==3) ? 0 : 3 ];
      GLfloat ex = VB.Win[j1][0] - VB.Win[j3][0];
      GLfloat ey = VB.Win[j1][1] - VB.Win[j3][1];
      GLfloat ez = VB.Win[j1][2] - VB.Win[j3][2];
      GLfloat fx = VB.Win[j2][0] - VB.Win[j0][0];
      GLfloat fy = VB.Win[j2][1] - VB.Win[j0][1];
      GLfloat fz = VB.Win[j2][2] - VB.Win[j0][2];
      GLfloat a, b, c, d;
      c = ex*fy-ey*fx;
      if (c==0.0F) {
         /* polygon is perpindicular to view plane, don't draw it */
         return;
      }

      /* compute orientation:  0=front, 1=back */
      facing = (c<0.0F) ^ odd_flag ^ (CC.Polygon.FrontFace==GL_CW);

      if (CC.Polygon.CullFlag && facing==(CC.Polygon.CullFaceMode==GL_BACK)) {
         return;   /* culled */
      }
      a = ey*fz-ez*fy;
      b = ez*fx-ex*fz;
      d = a*VB.Win[j0][0] + b*VB.Win[j0][1] + c*VB.Win[j0][2];
      if (CC.Polygon.OffsetEnabled) {
         OFFSET_POLYGON( a, b, c, d );
      }
      CC.PlaneA = a;
      CC.PlaneB = b;
      CC.PlaneC = c;
      CC.PlaneD = d;
   }
   else {
      CC.PlaneA = 0.0F;
      CC.PlaneB = 0.0F;
      CC.PlaneC = 1.0F;
      CC.PlaneD = 0.0F;
      facing = 0;
   }

   if (facing==1 && CC.Light.Enabled && CC.Light.Model.TwoSide) {
      /* use back color or index */
      VB.Color = VB.Bcolor;
      VB.Index = VB.Bindex;
   }
   else {
      /* use front color or index */
      VB.Color = VB.Fcolor;
      VB.Index = VB.Findex;
   }

   (*CC.PolygonFunc)( n, vlist, provoking_vertex );
}




/*
 * Render an un-clipped triangle.
 */
static void render_triangle( GLuint v0, GLuint v1, GLuint v2, GLuint pv,
                             GLuint odd_flag )
{
   GLuint facing;
   GLuint vlist[3];

   /* Compute the plane equation of polygon: ax + by + cz = d */
   if (CC.ComputePlane) {
      GLfloat ex = VB.Win[v1][0] - VB.Win[v0][0];
      GLfloat ey = VB.Win[v1][1] - VB.Win[v0][1];
      GLfloat ez = VB.Win[v1][2] - VB.Win[v0][2];
      GLfloat fx = VB.Win[v2][0] - VB.Win[v0][0];
      GLfloat fy = VB.Win[v2][1] - VB.Win[v0][1];
      GLfloat fz = VB.Win[v2][2] - VB.Win[v0][2];
      GLfloat a, b, c, d;
      c = ex*fy-ey*fx;
      if (c==0.0F) {
	 /* polygon is perpindicular to view plane, don't draw it */
	 return;
      }

      /* compute orientation:  0=front, 1=back */
      facing = (c<0.0F) ^ odd_flag ^ (CC.Polygon.FrontFace==GL_CW);

      if (CC.Polygon.CullFlag && facing==(CC.Polygon.CullFaceMode==GL_BACK)) {
	 return;   /* culled */
      }
      a = ey*fz-ez*fy;
      b = ez*fx-ex*fz;
      d = a*VB.Win[v0][0] + b*VB.Win[v0][1] + c*VB.Win[v0][2];
      if (CC.Polygon.OffsetEnabled) {
         OFFSET_POLYGON( a, b, c, d );
      }
      CC.PlaneA = a;
      CC.PlaneB = b;
      CC.PlaneC = c;
      CC.PlaneD = d;
   }
   else {
      CC.PlaneA = 0.0F;
      CC.PlaneB = 0.0F;
      CC.PlaneC = 1.0F;
      CC.PlaneD = 0.0F;
      facing = 0;
   }

   if (facing==1 && CC.Light.Enabled && CC.Light.Model.TwoSide) {
      /* use back color or index */
      VB.Color = VB.Bcolor;
      VB.Index = VB.Bindex;
   }
   else {
      /* use front color or index */
      VB.Color = VB.Fcolor;
      VB.Index = VB.Findex;
   }

   /* TODO: eventually call a special triangle rasterizer */
   vlist[0] = v0;
   vlist[1] = v1;
   vlist[2] = v2;
   (*CC.PolygonFunc)( 3, vlist, pv );
/*
   gl_smooth_rgba_triangle( v0, v1, v2, pv );
*/
}




/*
 * Render an un-clipped quadrilateral.
 */
static void render_quad( GLuint v0, GLuint v1, GLuint v2, GLuint v3,
                         GLuint pv, GLuint odd_flag )
{
   GLuint facing;
   GLuint vlist[4];

   /* Compute the plane equation of polygon: ax + by + cz = d */
   if (CC.ComputePlane) {
      GLfloat ex = VB.Win[v2][0] - VB.Win[v0][0];
      GLfloat ey = VB.Win[v2][1] - VB.Win[v0][1];
      GLfloat ez = VB.Win[v2][2] - VB.Win[v0][2];
      GLfloat fx = VB.Win[v3][0] - VB.Win[v1][0];
      GLfloat fy = VB.Win[v3][1] - VB.Win[v1][1];
      GLfloat fz = VB.Win[v3][2] - VB.Win[v1][2];
      GLfloat a, b, c, d;
      c = ex*fy-ey*fx;
      if (c==0.0F) {
	 /* polygon is perpindicular to view plane, don't draw it */
	 return;
      }
      
      /* compute orientation:  0=front, 1=back */
      facing = (c<0.0F) ^ odd_flag ^ (CC.Polygon.FrontFace==GL_CW);

      if (CC.Polygon.CullFlag && facing==(CC.Polygon.CullFaceMode==GL_BACK)) {
	 return;   /* culled */
      }
      a = ey*fz-ez*fy;
      b = ez*fx-ex*fz;
      d = a*VB.Win[v0][0] + b*VB.Win[v0][1] + c*VB.Win[v0][2];
      if (CC.Polygon.OffsetEnabled) {
         OFFSET_POLYGON( a, b, c, d );
      }
      CC.PlaneA = a;
      CC.PlaneB = b;
      CC.PlaneC = c;
      CC.PlaneD = d;
   }
   else {
      CC.PlaneA = 0.0F;
      CC.PlaneB = 0.0F;
      CC.PlaneC = 1.0F;
      CC.PlaneD = 0.0F;
      facing = 0;
   }

   if (facing==1 && CC.Light.Enabled && CC.Light.Model.TwoSide) {
      /* use back color or index */
      VB.Color = VB.Bcolor;
      VB.Index = VB.Bindex;
   }
   else {
      /* use front color or index */
      VB.Color = VB.Fcolor;
      VB.Index = VB.Findex;
   }

   /* TODO: eventually call a triangle rasterizer twice */
   vlist[0] = v0;
   vlist[1] = v1;
   vlist[2] = v2;
   vlist[3] = v3;
   (*CC.PolygonFunc)( 4, vlist, pv );
}



/*
 * When the vertex buffer is full, we transform/render it.  Sometimes we
 * have to copy the last vertex (or two) to the front of the vertex list
 * to "continue" the primitive.  For example:  line or triangle strips.
 * This function is a helper for that.
 */
static void copy_vertex( GLuint dst, GLuint src )
{
   COPY_3V( VB.Win[dst], VB.Win[src] );
   COPY_4V( VB.Eye[dst], VB.Eye[src] );
   COPY_4V( VB.Fcolor[dst], VB.Fcolor[src] );
   COPY_4V( VB.Bcolor[dst], VB.Bcolor[src] );
   VB.Findex[dst] = VB.Findex[src];
   VB.Bindex[dst] = VB.Bindex[src];
   VB.Edgeflag[dst] = VB.Edgeflag[src];
   COPY_4V( VB.TexCoord[dst], VB.TexCoord[src] );
}




/*
 * Either the vertex buffer is full (VB.Count==VB_MAX) or glEnd() has been
 * called.  Render the primitives defined by the vertices and reset the
 * buffer.
 * Input:  alldone - GL_TRUE = caller is glEnd()
 *                   GL_FALSE = calling because buffer is full.
 */
static void render_vb( GLboolean alldone )
{
   GLuint vlist[VB_MAX];

   switch (CC.Mode) {
      case GL_POINTS:
         (*CC.PointsFunc)( 0, VB.Count-1 );
	 VB.Count = 0;
	 break;

      case GL_LINES:
         if (VB.AnyClipped) {
            GLuint i;
            for (i=1;i<VB.Count;i+=2) {
               if (VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  (*CC.LineFunc)( i-1, i, i );
               }
               else {
                  render_clipped_line( i-1, i );
               }
               CC.StippleCounter = 0;
            }
         }
         else {
            GLuint i;
            for (i=1;i<VB.Count;i+=2) {
               (*CC.LineFunc)( i-1, i, i );
               CC.StippleCounter = 0;
            }
         }
	 VB.Count = 0;
	 break;

      case GL_LINE_STRIP:
	 if (VB.Count>1) {
            GLuint i;
	    for (i=1;i<VB.Count;i++) {
               if (VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  (*CC.LineFunc)( i-1, i, i );
               }
               else {
                  render_clipped_line( i-1, i );
               }
	    }
	    if (VB.Count==VB_MAX) {
               copy_vertex( 0, VB_MAX-1 );  /* copy last vertex to front */
	    }
	 }
	 VB.Count = 1;
         break;

      case GL_LINE_LOOP:
         {
            GLuint i;
            if (VB.Start==0) {
               i = 1;  /* start at 0th vertex */
            }
            else {
               i = 2;  /* skip first vertex, we're saving it until glEnd */
            }
            while (i<VB.Count) {
               if (VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  (*CC.LineFunc)( i-1, i, i );
               }
               else {
                  render_clipped_line( i-1, i );
               }
               i++;
            }
         }
	 if (alldone) {
            if (VB.Unclipped[VB.Count-1] & VB.Unclipped[0]) {
               (*CC.LineFunc)( VB.Count-1, 0, 0 );
            }
            else {
               render_clipped_line( VB.Count-1, 0 );
            }
	 }
	 else {
	    ASSERT(VB.Count==VB_MAX);
	    /* recycle the vertex list */
            copy_vertex( 1, VB_MAX-1 );
	    VB.Count = 2;
	 }
         break;

      case GL_TRIANGLES:
         {
            GLuint i;
            for (i=2;i<VB.Count;i+=3) {
               if (VB.Unclipped[i-2] & VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  render_triangle( i-2, i-1, i, i, 0 );
               }
               else {
                  vlist[0] = i-2;
                  vlist[1] = i-1;
                  vlist[2] = i-0;
                  render_clipped_polygon( 3, vlist, 0 );
               }
            }
         }
	 VB.Count = 0;
	 break;

      case GL_TRIANGLE_STRIP:
         if (VB.AnyClipped) {
            GLuint i;
            for (i=2;i<VB.Count;i++) {
               if (VB.Unclipped[i-2] & VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  render_triangle( i-2, i-1, i, i, i&1 );
               }
               else {
                  vlist[0] = i-2;
                  vlist[1] = i-1;
                  vlist[2] = i-0;
                  render_clipped_polygon( 3, vlist, i&1 );
               }
            }
            /* TODO: VB.AnyClipped might fail next time */
         }
         else {
            GLuint i;
            for (i=2;i<VB.Count;i++) {
               render_triangle( i-2, i-1, i, i, i&1 );
            }
         }
         if (!alldone) {
            /* get ready for more vertices in this triangle strip */
            copy_vertex( 0, VB_MAX-2 );
            copy_vertex( 1, VB_MAX-1 );
            VB.Count = 2;
         }
	 break;

      case GL_TRIANGLE_FAN:
         {
            GLuint i;
            for (i=2;i<VB.Count;i++) {
               if (VB.Unclipped[0] & VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  render_triangle( 0, i-1, i, i, 0 );
               }
               else {
                  vlist[0] = 0;
                  vlist[1] = i-1;
                  vlist[2] = i;
                  render_clipped_polygon( 3, vlist, 0 );
               }
            }
         }
         if (!alldone) {
            /* get ready for more vertices in this triangle fan */
            copy_vertex( 1, VB_MAX-1 );
            VB.Count = 2;
	 }
	 break;

      case GL_QUADS:
         if (VB.AnyClipped) {
            GLuint i;
            for (i=3;i<VB.Count;i+=4) {
               if (  VB.Unclipped[i-3] & VB.Unclipped[i-2]
                   & VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  render_quad( i-3, i-2, i-1, i, i, 0 );
               }
               else {
                  vlist[0] = i-3;
                  vlist[1] = i-2;
                  vlist[2] = i-1;
                  vlist[3] = i-0;
                  render_clipped_polygon( 4, vlist, 0 );
               }
            }
         }
         else {
            GLuint i;
            for (i=3;i<VB.Count;i+=4) {
               render_quad( i-3, i-2, i-1, i, i, 0 );
            }
         }
	 VB.Count = 0;
	 break;

      case GL_QUAD_STRIP:
         {
            GLuint i;
            for (i=3;i<VB.Count;i+=2) {
               if (  VB.Unclipped[i-2] & VB.Unclipped[i-3]
                   & VB.Unclipped[i-1] & VB.Unclipped[i]) {
                  render_quad( i-2, i-3, i-1, i, i, 1 );
               }
               else {
                  vlist[0] = i-2;
                  vlist[1] = i-3;
                  vlist[2] = i-1;
                  vlist[3] = i-0;
                  render_clipped_polygon( 4, vlist, 1 );
               }
            }
         }
         if (!alldone) {
            /* get ready for more vertices in this quad strip */
            copy_vertex( 0, VB_MAX-2 );
            copy_vertex( 1, VB_MAX-1 );
            VB.Count = 2;
         }
	 break;

      case GL_POLYGON:
         {
            GLuint i;
            for (i=0;i<VB.Count;i++) {
               vlist[i] = i;
            }
            if (VB.AnyClipped) {
               render_clipped_polygon( VB.Count, vlist, 0 );
            }
            else {
               render_polygon( VB.Count, vlist, 0 );
            }
         }
	 if (!alldone) {
            /* get ready for more vertices just like a triangle fan */
            copy_vertex( 1, VB_MAX-1 );
            VB.Count = 2;
	 }
	 break;

      default:
         /* should never get here */
	 abort();
   }

   /* Start = first vertex which hasn't been transformed yet */
   VB.Start = VB.Count;
}



/*
 * When the Vertex Buffer is full, this function transforms all the
 * vertices, normals, computes lighting, clipflags, fog, texture coords, etc.
 * The hope is to maximize floating point performance inside the loops.
 */
static void xform_vb( GLboolean alldone )
{
   ASSERT( VB.Count>0 );

   /* Transform vertexes from object to eye coords */
   gl_transform_points( VB.Count-VB.Start, VB.Eye+VB.Start,
                        CC.ModelViewMatrix, VB.Obj+VB.Start );
      
   /* Transform normals from object to eye coords */
   if (CC.NeedNormals) {
      gl_transform_normals( VB.Count-VB.Start,
                            VB.Normal+VB.Start, CC.ModelViewInv,
                            VB.Normal+VB.Start, CC.Transform.Normalize );
   }

   /* Lighting */
   if (CC.Light.Enabled) {
      if (CC.RGBAflag) {
         if (VB.MaterialChanges) {
            GLuint i;
            /* NOTE the <= here.  This is needed in case glColor/glMaterial
             * is called after the last glVertex inside a glBegin/glEnd pair.
             */
	    for (i=VB.Start;i<=VB.Count;i++) {
               update_material( i );
	       gl_color_shade( VB.Eye[i], VB.Normal[i], CC.LightTwoSide,
			       VB.Fcolor[i], VB.Bcolor[i] );
	    }
	 }
         else {
            gl_color_shade_vertices( VB.Count-VB.Start,
                                     VB.Eye + VB.Start,
                                     VB.Normal + VB.Start,
                                     CC.LightTwoSide,
                                     VB.Fcolor + VB.Start,
                                     VB.Bcolor + VB.Start );
	 }
      }
      else {
         if (VB.MaterialChanges) {
            GLuint i;
            /* NOTE the <= here.  This is needed in case glColor/glMaterial
             * is called after the last glVertex inside a glBegin/glEnd pair.
             */
            for (i=VB.Start;i<=VB.Count;i++) {
               update_material( i );
               gl_index_shade( VB.Eye[i], VB.Normal[i], CC.LightTwoSide,
                               &VB.Findex[i], &VB.Bindex[i] );
            }
         }
         else {
            GLuint i;
            for (i=VB.Start;i<VB.Count;i++) {
               gl_index_shade( VB.Eye[i], VB.Normal[i], CC.LightTwoSide,
                               &VB.Findex[i], &VB.Bindex[i] );
            }
         }
      }
   }

   /* Per-vertex fog */
   if (CC.Fog.Enabled && CC.Hint.Fog!=GL_NICEST) {
      if (CC.RGBAflag) {
         /* Fog RGB colors */
         gl_fog_color_vertices( VB.Count - VB.Start,
                                VB.Eye + VB.Start,
                                VB.Fcolor + VB.Start );
         if (CC.LightTwoSide) {
            gl_fog_color_vertices( VB.Count - VB.Start,
                                   VB.Eye + VB.Start,
                                   VB.Bcolor + VB.Start );
         }
      }
      else {
         /* Fog color indexes */
         gl_fog_index_vertices( VB.Count - VB.Start,
                                VB.Eye + VB.Start,
                                VB.Findex + VB.Start );
         if (CC.LightTwoSide) {
            gl_fog_index_vertices( VB.Count - VB.Start,
                                   VB.Eye + VB.Start,
                                   VB.Bindex + VB.Start );
         }
      }
   }

   /* Compute/transform texture coords */
   if (CC.Texture.Enabled) {
      GLuint i;
      for (i=VB.Start;i<VB.Count;i++) {
         if (CC.Texture.TexGenEnabled) {
            gl_do_texgen( VB.Obj[i], VB.Eye[i], VB.Normal[i], VB.TexCoord[i] );
         }
         if (!CC.IdentityTexMat) {
            /* transform current texture coordinate by texture matrix */
            /* tc = TexMat * TexCoord */
            GLfloat tc[4];
            TRANSFORM_POINT( tc, CC.TextureMatrix, VB.TexCoord[i] );
            COPY_4V( VB.TexCoord[i], tc );
         }
      }
   }

   if (CC.Transform.AnyClip) {
      /* Clip against user-defined clip planes */
      GLuint p;
      VB.AnyClipped = GL_FALSE;
      for (p=0;p<MAX_CLIP_PLANES;p++) {
         if (CC.Transform.ClipEnabled[p]) {
            GLuint i;
            GLfloat a = CC.Transform.ClipEquation[p][0];
            GLfloat b = CC.Transform.ClipEquation[p][1];
            GLfloat c = CC.Transform.ClipEquation[p][2];
            GLfloat d = CC.Transform.ClipEquation[p][3];
            for (i=VB.Start;i<VB.Count;i++) {
               GLfloat dot = VB.Eye[i][0] * a + VB.Eye[i][1] * b
                           + VB.Eye[i][2] * c + VB.Eye[i][3] * d;
               if (dot < 0.0F) {
                  VB.Unclipped[i] = GL_FALSE;
                  VB.AnyClipped = GL_TRUE;
               }
               else {
                  VB.Unclipped[i] = GL_TRUE;
               }
            }
         }
      }
   }
   else {
      /* Initialize clip flags */
      MEMSET( VB.Unclipped+VB.Start, 1, VB.Count-VB.Start );
      VB.AnyClipped = GL_FALSE;
   }

   /* Transform vertices from eye to clip coords */
   /* Even transform clipped vertices because it's usually faster. */
   gl_transform_points( VB.Count-VB.Start, VB.Clip+VB.Start,
                        CC.ProjectionMatrix, VB.Eye+VB.Start );

   /* Clip vertices against view volume */
   {
      GLuint i;
      for (i=VB.Start;i<VB.Count;i++) {
         /* Unclipped[i] will remain TRUE if vertex is inside view volume */
         if (VB.Clip[i][0] > VB.Clip[i][3] || VB.Clip[i][0] < -VB.Clip[i][3] ||
             VB.Clip[i][1] > VB.Clip[i][3] || VB.Clip[i][1] < -VB.Clip[i][3] ||
             VB.Clip[i][2] > VB.Clip[i][3] || VB.Clip[i][2] < -VB.Clip[i][3] ){
            VB.Unclipped[i] = GL_FALSE;
            VB.AnyClipped = GL_TRUE;
         }
      }
   }
      
   /* Map vertices from clip to window coords */
   if (VB.AnyClipped) {
      GLuint i;
      GLfloat sx = CC.Viewport.Sx,  tx = CC.Viewport.Tx;
      GLfloat sy = CC.Viewport.Sy,  ty = CC.Viewport.Ty;
      GLfloat sz = CC.Viewport.Sz,  tz = CC.Viewport.Tz;
      for (i=VB.Start;i<VB.Count;i++) {
         if (VB.Unclipped[i]) {
            GLfloat d;
            ASSERT( VB.Clip[i][3] != 0.0 );
            d = 1.0F / VB.Clip[i][3];
            VB.Win[i][0] = VB.Clip[i][0] * d * sx + tx;
            VB.Win[i][1] = VB.Clip[i][1] * d * sy + ty;
            VB.Win[i][2] = VB.Clip[i][2] * d * sz + tz;
         }
      }
   }
   else {
      /* This is written to unroll with IRIX 5.3 cc */
      GLuint i = VB.Start, n = VB.Count;
      GLfloat sx = CC.Viewport.Sx,  tx = CC.Viewport.Tx;
      GLfloat sy = CC.Viewport.Sy,  ty = CC.Viewport.Ty;
      GLfloat sz = CC.Viewport.Sz,  tz = CC.Viewport.Tz;
      while (i!=n) {
         GLfloat d = 1.0F / VB.Clip[i][3];
         ASSERT( VB.Clip[i][3] != 0.0 );
         VB.Win[i][0] = VB.Clip[i][0] * d * sx + tx;
         VB.Win[i][1] = VB.Clip[i][1] * d * sy + ty;
         VB.Win[i][2] = VB.Clip[i][2] * d * sz + tz;
         i++;
      }
   }

   /* Render the primitives */
   render_vb( alldone );
}



/*
 * Copy the vertex and associated data into the VB.  When the VB is
 * full, render the primitives.
 */
void gl_execute_vertex( GLfloat x, GLfloat y, GLfloat z, GLfloat w )
{
   ASSIGN_4V( VB.Obj[VB.Count], x, y, z, w );
   if (CC.Light.Enabled) {
      COPY_3V( VB.Normal[VB.Count], CC.Current.Normal );
   }
   if (CC.RGBAflag) {
      COPY_4V( VB.Fcolor[VB.Count], CC.Current.Color );
      if (CC.Texture.Enabled) {
         COPY_4V( VB.TexCoord[VB.Count], CC.Current.TexCoord );
      }
   }
   else {
      VB.Findex[VB.Count] = (GLfloat) CC.Current.Index;
   }
   VB.Edgeflag[VB.Count] = CC.Current.EdgeFlag;

   VB.Count++;
   if (VB.Count==VB_MAX) {
      xform_vb( GL_FALSE );
   }
}


/*
 * Save a glVertex call into a display list AND execute it.
 */
void gl_save_and_execute_vertex( GLfloat x, GLfloat y, GLfloat z, GLfloat w )
{
   gl_save_vertex( x, y, z, w );
   gl_execute_vertex( x, y, z, w );
}




/*
 * Process a vertex produced by an evaluator.
 * Input:  v - vertex
 *         n - normal
 *         c - color
 *         i - color index
 *         t - texture coordinate
 */
void gl_eval_vertex( const GLfloat v[4], const GLfloat n[3],
		     const GLfloat c[4], GLfloat i, GLfloat t[4] )
{
   COPY_4V( VB.Obj[VB.Count], v );
   COPY_3V( VB.Normal[VB.Count], n );
   COPY_4V( VB.Fcolor[VB.Count], c );
   VB.Findex[VB.Count] = i;
   COPY_4V( VB.TexCoord[VB.Count], t );
   VB.Edgeflag[VB.Count] = CC.Current.EdgeFlag;

   VB.Count++;
   if (VB.Count==VB_MAX) {
      xform_vb( GL_FALSE );
   }
}




void gl_rasterpos( const GLfloat v[4] )
{
   GLfloat eye[4], clip[4], ndc[3], d;

   /* transform v to eye coords:  eye = ModelView * v */
   TRANSFORM_POINT( eye, CC.ModelViewMatrix, v );

   /* compute lighting */
   if (CC.Light.Enabled) {
      GLfloat eyenorm[3];
      if (!CC.ModelViewInvValid) {
	 gl_compute_modelview_inverse();
      }
      TRANSFORM_NORMAL( eyenorm[0], eyenorm[1], eyenorm[2], CC.Current.Normal,
                        CC.ModelViewInv );
      if (CC.RGBAflag) {
	 gl_color_shade( eye, eyenorm, 0, CC.Current.RasterColor, NULL );
      }
      else {
	 GLfloat indx;
	 gl_index_shade( eye, eyenorm, 0, &indx, NULL );
	 CC.Current.RasterIndex = (GLuint) (GLint) indx;
      }
   }
   else {
      /* copy current color or index */
      if (CC.RGBAflag) {
	 COPY_4V( CC.Current.RasterColor, CC.Current.Color );
      }
      else {
	 CC.Current.RasterIndex = CC.Current.Index;
      }
   }

   /* clip to user clipping planes */
   if (gl_userclip_point(eye)==0) {
      CC.Current.RasterPosValid = GL_FALSE;
      return;
   }

   /* compute raster distance */
   CC.Current.RasterDistance = (GLfloat)
                      sqrt( eye[0]*eye[0] + eye[1]*eye[1] + eye[2]*eye[2] );

   /* apply projection matrix:  clip = Proj * eye */
   TRANSFORM_POINT( clip, CC.ProjectionMatrix, eye );

   /* clip to view volume */
   if (gl_viewclip_point( clip )==0) {
      CC.Current.RasterPosValid = GL_FALSE;
      return;
   }

   /* ndc = clip / W */
   ASSERT( clip[3]!=0.0 );
   d = 1.0F / clip[3];
   ndc[0] = clip[0] * d;
   ndc[1] = clip[1] * d;
   ndc[2] = clip[2] * d;

   CC.Current.RasterPos[0] = MAP_X( ndc[0] );
   CC.Current.RasterPos[1] = MAP_Y( ndc[1] );
   CC.Current.RasterPos[2] = MAP_Z( ndc[2] );
   CC.Current.RasterPos[3] = clip[3];
   CC.Current.RasterPosValid = GL_TRUE;

   /* FOG??? */

   if (CC.Texture.Enabled) {
      /* TODO: Current.RasterTexCoord */

   }

   if (CC.RenderMode==GL_SELECT) {
      /* TODO: is this correct? */
      CC.HitFlag = GL_TRUE;
      if (CC.Current.RasterPos[2] < CC.HitMinZ) {
	 CC.HitMinZ = CC.Current.RasterPos[2];
      }
      if (CC.Current.RasterPos[2] < CC.HitMaxZ) {
	 CC.HitMaxZ = CC.Current.RasterPos[2];
      }
   }

}



void gl_index( GLuint index )
{
   /* TODO: inline this assignment! */
   CC.Current.Index = index;
   VB.MonoColor = GL_FALSE;
}



void gl_color( const GLfloat color[4] )
{
   /* TODO: inline this assignment! */
   COPY_4V( CC.Current.Color, color );
   if (CC.Light.Enabled && CC.Light.ColorMaterialEnabled) {
      /* Translate this glColor() call into a glMaterial() call */
      gl_material( CC.Light.ColorMaterialFace,
                   CC.Light.ColorMaterialMode, color );
   }
   VB.MonoColor = GL_FALSE;
}



void gl_begin( GLenum p )
{
   CC.Mode = p;
   VB.Start = VB.Count = 0;

   if (!CC.ModelViewInvValid) {
      gl_compute_modelview_inverse();
   }
   if (CC.NewState) {
      gl_update_state();
   }

   VB.MonoColor = !CC.Light.Enabled && !CC.Texture.Enabled
                && !CC.Color.BlendEnabled && !CC.Color.SWLogicOpEnabled;
   if (VB.MonoColor) {
      if (CC.RGBAflag) {
         GLubyte r = (GLint) (CC.Current.Color[0] * CC.RedScale);
         GLubyte g = (GLint) (CC.Current.Color[1] * CC.GreenScale);
         GLubyte b = (GLint) (CC.Current.Color[2] * CC.BlueScale);
         GLubyte a = (GLint) (CC.Current.Color[3] * CC.AlphaScale);
         if (CC.RasterMask & GAMMA_BIT) {
            gl_apply_gamma( 1, &r, &g, &b );
         }
         (*DD.color)( r, g, b, a );
      }
      else {
         (*DD.index)(CC.Current.Index);
      }
   }

   switch (CC.Mode) {
      case GL_POINTS:
	 CC.LightTwoSide = 0;
	 VB.Color = VB.Fcolor;
	 VB.Index = VB.Findex;
	 gl_init_pb( GL_POINT );
	 break;
      case GL_LINES:
      case GL_LINE_STRIP:
      case GL_LINE_LOOP:
	 CC.LightTwoSide = 0;
	 VB.Color = VB.Fcolor;
	 VB.Index = VB.Findex;
	 CC.StippleCounter = 0;
	 gl_init_pb( GL_LINE );
         break;
      case GL_TRIANGLES:
      case GL_TRIANGLE_STRIP:
      case GL_TRIANGLE_FAN:
      case GL_QUADS:
      case GL_QUAD_STRIP:
      case GL_POLYGON:
	 CC.LightTwoSide = (GLuint) CC.Light.Model.TwoSide;
	 gl_init_pb( GL_POLYGON );
         break;
      default:
	 gl_error( GL_INVALID_ENUM, "glBegin" );
	 CC.Mode = GL_BITMAP;
   }
}



void gl_end( void )
{
   if (CC.Mode==GL_BITMAP) {
      /* glEnd without glBegin */
      gl_error( GL_INVALID_OPERATION, "glEnd" );
      return;
   }

   if (VB.Count>VB.Start) {
      xform_vb( GL_TRUE );
   }
   if (PB.count>0) {
      gl_flush_pb();
   }
   PB.primitive = CC.Mode = GL_BITMAP;  /* Default mode */
   VB.MaterialChanges = GL_FALSE;
}




/*
 *
 * Public functions
 *
 */

void glBegin( GLenum p )
{
   if (CC.CompileFlag) {
      gl_save_begin( p );
   }
   if (CC.ExecuteFlag) {
      gl_begin( p );
   }
}



void glEnd( void )
{
   if (CC.CompileFlag) {
      gl_save_end();
   }
   if (CC.ExecuteFlag) {
      gl_end();
   }
}
