/* $Id: vbcull.c,v 1.7 1999/11/08 15:28:58 brianp Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  3.1
 * 
 * Copyright (C) 1999  Brian Paul   All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*
 * New (3.1) transformation code written by Keith Whitwell.
 */


#ifdef PC_HEADER
#include "all.h"
#else
#ifndef XFree86Server
#include <stdio.h>
#else
#include "GL/xf86glx.h"
#endif
#include "context.h"
#include "macros.h"
#include "types.h"
#include "vb.h"
#include "vbcull.h"
#include "vbrender.h"
#include "xform.h"
#endif


/* Culling for vertices and primitives.
 * (C) Keith Whitwell, December 1998.
 *
 * These functions build bitmasks which can be used to shortcircuit
 * lighting, fogging and texture calculations, and to convert
 * two-sided lighting into one-sided on a per-vertex basis.
 *
 * Each of these functions returns a value 'cullcount'.  The meaning
 * of this value just has to satisfy the requirements:
 *
 *    cullcount == VB->Count implies the entire vb has been culled and
 *    can be discarded.
 * 
 *    cullcount == 0 implies that every vertex in the buffer is required
 *    and that the cullmask can be ignored for transformation 
 *    and lighting.
 * 
 */


const char *gl_prim_name[GL_POLYGON+2] = {
   "GL_POINTS",
   "GL_LINES",
   "GL_LINE_LOOP",
   "GL_LINE_STRIP",
   "GL_TRIANGLES",
   "GL_TRIANGLE_STRIP",
   "GL_TRIANGLE_FAN",
   "GL_QUADS",
   "GL_QUAD_STRIP",
   "GL_POLYGON",
   "culled primitive"
};

static GLuint gl_cull_points( struct vertex_buffer *VB,
			      GLuint start, 
			      GLuint count,
			      GLuint parity,
			      CONST GLfloat (*proj)[4])
{
   const GLubyte *clipmask = VB->ClipMask;
   GLubyte *cullmask = VB->CullMask;
   GLuint i, cullcount = 0;

   (void) parity;
   (void) proj;

   /* This is pretty pointless.  (arf arf)
    */
   for (i=start+1;i<count;i++) {
      if (clipmask[i] == 0) 
	 cullmask[i] = VERT_FACE_FRONT | PRIM_FACE_FRONT;
      else {
	 cullcount++;
      }
   }
   return cullcount;
}


/* Covers all the non-polygonal primitives when only area culling is
 * activated.
 */
static GLuint gl_cull_points_neither( struct vertex_buffer *VB,
				      GLuint start, 
				      GLuint count,
				      GLuint parity,
				      CONST GLfloat (*proj)[4])
{
   GLubyte unculled_prim = VERT_FACE_FRONT|PRIM_FACE_FRONT;
   (void) parity;
   (void) proj;
   MEMSET(VB->CullMask+start, unculled_prim, (count-start)*sizeof(GLubyte));
   return 0;
}



#define CULL_LINE( i1, i, nr )						 \
do {									 \
   GLubyte ClipOr = VB->ClipMask[i1] | VB->ClipMask[i];			 \
									 \
   if (ClipOr) {							 \
      if (VB->ClipMask[i1] & VB->ClipMask[i] & CLIP_ALL_BITS) {		 \
	 cullcount+=nr;							 \
      }									 \
      else {								 \
	 cullmask[i1] |= VERT_FACE_FRONT;				 \
	 cullmask[i] |= VERT_FACE_FRONT | PRIM_FACE_FRONT | PRIM_CLIPPED; \
      }									 \
   } else {								 \
      cullmask[i1] |= VERT_FACE_FRONT;					 \
      cullmask[i] |= VERT_FACE_FRONT | PRIM_FACE_FRONT;			 \
   }									 \
} while (0)



static GLuint gl_cull_lines( struct vertex_buffer *VB,
			     GLuint start, 
			     GLuint count, 
			     GLuint parity, 
			     CONST GLfloat (*proj)[4])
{
   GLubyte *cullmask = VB->CullMask;
   GLuint cullcount = 0;
   GLuint i;
   GLuint last = count - 1;

   (void) parity;
   (void) proj;

   for (i = start; i < last ; i += 2) {
      CULL_LINE(i, i+1, 2);
   }

   if (i != last)
      cullcount++;

   return cullcount;
}


static GLuint gl_cull_line_strip( struct vertex_buffer *VB,
				  GLuint start, 
				  GLuint count, 
				  GLuint parity, 
				  CONST GLfloat (*proj)[4])
{
   GLubyte *cullmask = VB->CullMask;
   GLuint cullcount = 0;
   GLuint i;
   GLuint last = count - 1;
   GLuint nr = 2;

   (void) parity;
   (void) proj;
   
   for (i = start; i < last ; i++, nr = 1) {
      CULL_LINE(i, i+1, nr);
   }

   if (i != last)
      cullcount++;

   return cullcount;
}


static GLuint gl_cull_line_loop( struct vertex_buffer *VB,
				 GLuint start, 
				 GLuint count, 
				 GLuint parity, 
				 CONST GLfloat (*proj)[4])
{
   GLuint cullcount = 0;
   GLubyte *cullmask = VB->CullMask;
   GLuint i, last = count - 1;
   (void) parity;
   (void) proj;
   if (count - start < 2) 
      return count - start;

   for (i = start; i < last ; i++) {
      CULL_LINE(i, i+1, 1);
   }

   CULL_LINE(last, start, 1);

   return cullcount;
}


#define CULL_TRI( do_clip, do_area, i2, i1, i0, parity, nr )		\
do {									\
   GLubyte ClipOr = 0;							\
   if (do_clip) {							\
      ClipOr = (VB->ClipMask[i2] |					\
		VB->ClipMask[i1] |					\
		VB->ClipMask[i0]);					\
   }									\
									\
   if (do_clip && (ClipOr&CLIP_ALL_BITS))				\
   {									\
      if (VB->ClipMask[i2] & VB->ClipMask[i1] & VB->ClipMask[i0] &	\
	  CLIP_ALL_BITS)						\
      {									\
	 cullcount+=nr;							\
      }									\
      else								\
      {									\
	 cullmask[i0]   = cull_faces | PRIM_CLIPPED;			\
	 cullmask[i1] |= cull_faces;					\
	 cullmask[i2] |= cull_faces;					\
      }									\
   }									\
   else									\
   {									\
      GLfloat area;							\
      GLubyte face_flags;						\
									\
      if (do_area) {							\
	 area = ((proj[i2][0] - proj[i0][0]) *				\
		 (proj[i1][1] - proj[i0][1]) -				\
		 (proj[i2][1] - proj[i0][1]) *				\
		 (proj[i1][0] - proj[i0][0]));				\
									\
	 face_flags = (((area<0.0F) ^ parity) + 1) & cull_faces;	\
      } else {								\
	 face_flags = cull_faces;					\
      }									\
									\
      if (!do_area || face_flags)					\
      {									\
	 cullmask[i0]  = face_flags | (face_flags << PRIM_FLAG_SHIFT);	\
	 cullmask[i1] |= face_flags;					\
	 cullmask[i2] |= face_flags;					\
	 if (do_clip && ClipOr) cullmask[i0] |= PRIM_CLIPPED;		\
      }									\
      else								\
      {									\
	 cullcount+=nr;							\
      }									\
   }									\
} while (0)





#define CULL_QUAD( do_clip, do_area, i3, i2, i1, i, nr )		\
do {									\
   GLubyte ClipOr = 0;							\
									\
   if (do_clip) {							\
      ClipOr = (VB->ClipMask[i3] |					\
		VB->ClipMask[i2] | 					\
		VB->ClipMask[i1] | 					\
		VB->ClipMask[i]);					\
   }									\
									\
   if (do_clip && (ClipOr&CLIP_ALL_BITS))				\
   {									\
      if (VB->ClipMask[i3] & VB->ClipMask[i2] & 			\
	  VB->ClipMask[i1] & VB->ClipMask[i] &				\
	  CLIP_ALL_BITS) 						\
      {									\
	 cullcount+=nr;							\
      }									\
      else								\
      {									\
	 cullmask[i] = cull_faces | PRIM_CLIPPED;			\
	 cullmask[i1] = cull_faces | PRIM_CLIPPED; 			\
	 cullmask[i2] |= cull_faces; 			\
	 cullmask[i3] |= cull_faces; 					\
      }									\
   }									\
   else 								\
   {									\
      GLfloat area;							\
      GLubyte face_flags;						\
      									\
      if (do_area) {							\
	 area = ((proj[i1][0] - proj[i3][0]) * /* ex */			\
		 (proj[i ][1] - proj[i2][1]) - /* fy */			\
		 (proj[i1][1] - proj[i3][1]) * /* ey */			\
		 (proj[i ][0] - proj[i2][0])); /* fx */			\
	    								\
	 face_flags = (((area<0.0F) ^ face_bit) + 1) & cull_faces;	\
      } else {								\
	 face_flags = cull_faces;					\
      }									\
   									\
      if (do_area && !face_flags)					\
      {									\
	 cullcount+=nr;							\
      }									\
      else 								\
      {									\
	 cullmask[i]  = face_flags | (face_flags<<PRIM_FLAG_SHIFT);	\
	 cullmask[i1] = face_flags | (face_flags<<PRIM_FLAG_SHIFT);	\
	 cullmask[i2] |= face_flags;					\
	 cullmask[i3] |= face_flags;					\
	 if (ClipOr)							\
	    cullmask[i] |= PRIM_CLIPPED;				\
      } 								\
   }									\
} while(0)


#define DO_AREA 1
#define DO_CLIP 1
#define TAG(x) x
#include "cull_tmp.h"


#define DO_AREA 1
#define DO_CLIP 0
#define TAG(x) x##_area
#include "cull_tmp.h"

#define DO_AREA 0
#define DO_CLIP 1
#define TAG(x) x##_clip
#include "cull_tmp.h"



static GLuint gl_cull_dummy_prim( struct vertex_buffer *VB,
				  GLuint start, 
				  GLuint count, 
				  GLuint parity, 
				  CONST GLfloat (*proj)[4])
{
   (void) VB;
   (void) parity;
   (void) proj;
   return count - start;
}


/* KW:  Force the vertices which are used in both this and the
 * next vertex buffer to be fully transformed on their first 
 * appearance.  Need to do this because otherwise I would have
 * to try to copy the un-transformed (Obj, Normal) data.  This
 * is difficult in the case of shared normals, and seems
 * to be quite complex in the immediate mode case.  For now,
 * just force the copied vertices to be transformed.
 *
 * KW: Copy now organized to grow towards zero.
 */
static GLuint gl_copy_last_cull( struct vertex_buffer *VB, 
				 GLuint start, GLuint count,
				 GLuint ovf, CONST GLfloat (*proj)[4])
{
   const GLcontext *ctx = VB->ctx;
   GLubyte *cullmask = VB->CullMask;
   GLuint rv = 0;
   (void) start;
   (void) ovf;
   if (cullmask[count-1] == 0) {
      rv++;
      cullmask[count-1] = ctx->Polygon.CullBits;
   }
   VB->CopyCount = 1;
   VB->Copy[2] = count-1;
   COPY_4FV(VB->CopyProj[2], proj[count-1]);
   return rv;
}

static GLuint gl_copy_first_and_last_cull( struct vertex_buffer *VB, 
					   GLuint start, 
					   GLuint count, GLuint ovf,
					   CONST GLfloat (*proj)[4] )
{
   const GLcontext *ctx = VB->ctx;
   GLuint rv = 0;
   GLubyte *cullmask = VB->CullMask;
   (void) ovf;
   if (cullmask[count-1] == 0) {
      rv++;
      cullmask[count-1] = ctx->Polygon.CullBits;
   }
   if (cullmask[start] == 0) {
      rv++;
      cullmask[start] = ctx->Polygon.CullBits;
   }
   VB->CopyCount = 2;
   VB->Copy[1] = start;
   VB->Copy[2] = count-1;
   COPY_4FV(VB->CopyProj[1], proj[start]);
   COPY_4FV(VB->CopyProj[2], proj[count-1]);
   return rv;
}

/* Must be able to cope with zero or one overflow
 * 
 */
static GLuint gl_copy_last_two_cull( struct vertex_buffer *VB,
				     GLuint start, 
				     GLuint count, GLuint ovf,
				     CONST GLfloat (*proj)[4] )
{
   const GLcontext *ctx = VB->ctx;
   GLubyte *cullmask = VB->CullMask;
   GLuint rv = 0;
   (void) start;
   if (cullmask[count-1] == 0) {
      rv++;
      cullmask[count-1] = ctx->Polygon.CullBits;
   }
   if (cullmask[count-2] == 0) {
      rv++;
      cullmask[count-2] = ctx->Polygon.CullBits;
   }
   
   VB->CopyCount = 2;
   VB->Copy[1] = count-2;
   VB->Copy[2] = count-1;
   COPY_4FV(VB->CopyProj[1], proj[count-2]);
   COPY_4FV(VB->CopyProj[2], proj[count-1]);

   if (ovf == 1) {
      if (cullmask[count-3] == 0) {
	 rv++;
	 cullmask[count-3] = ctx->Polygon.CullBits;
      }
      VB->CopyCount = 3;
      VB->Copy[0] = count-3;
      COPY_4FV(VB->CopyProj[0], proj[count-3]);
   }

   return rv;
}


/* Eg, quads - just copy the overflow, guarenteed to all be culled.
 */
static GLuint gl_copy_overflow_cull( struct vertex_buffer *VB, 
				     GLuint start, GLuint count,
				     GLuint ovf, CONST GLfloat (*proj)[4] )
{
   const GLcontext *ctx = VB->ctx;
   GLubyte *cullmask = VB->CullMask;
   GLuint src_offset = count - ovf;
   GLuint dst_offset = 3 - ovf;
   GLuint i;
   (void) start;
   VB->CopyCount = ovf;
   for (i = 0 ; i < ovf ; i++) {
      cullmask[i+src_offset] = ctx->Polygon.CullBits;
      VB->Copy[i+dst_offset] = i+src_offset;
      COPY_4FV(VB->CopyProj[i+dst_offset], proj[i+src_offset]);
   }
   return ovf;
}


static GLuint gl_copy_last( struct vertex_buffer *VB,
			    GLuint start, GLuint count,
			    GLuint ovf, CONST GLfloat (*proj)[4])
{
   (void) start;
   (void) ovf;
   VB->CopyCount = 1;
   VB->Copy[2] = count-1;
   COPY_4FV(VB->CopyProj[2], proj[count-1]);
   return 0;
}

static GLuint gl_copy_first_and_last( struct vertex_buffer *VB, 
				      GLuint start, 
				      GLuint count,
				      GLuint ovf, CONST GLfloat (*proj)[4])
{
   (void) ovf;
   VB->CopyCount = 2;
   VB->Copy[1] = start;
   VB->Copy[2] = count-1;
   COPY_4FV(VB->CopyProj[1], proj[start]);
   COPY_4FV(VB->CopyProj[2], proj[count-1]);
   return 0;
}

static GLuint gl_copy_last_two( struct vertex_buffer *VB, 
				GLuint start, GLuint count, 
				GLuint ovf, CONST GLfloat (*proj)[4])
{
   (void) start;
   VB->CopyCount = 2;
   VB->Copy[1] = count-2;
   VB->Copy[2] = count-1;
   COPY_4FV(VB->CopyProj[1], proj[count-2]);
   COPY_4FV(VB->CopyProj[2], proj[count-1]);

   if (ovf == 1) {
      VB->CopyCount = 3;
      VB->Copy[0] = count-3;
      COPY_4FV(VB->CopyProj[0], proj[count-3]);
   }

   return 0;
}

static GLuint gl_copy_overflow( struct vertex_buffer *VB, 
				GLuint start, 
				GLuint count,
				GLuint ovf,
				CONST GLfloat (*proj)[4] )
{
   GLuint src_offset = count - ovf;
   GLuint dst_offset = 3 - ovf;
   GLuint i;
   (void) start;
   VB->CopyCount = ovf;
   for (i = 0 ; i < ovf ; i++) {
      VB->Copy[i+dst_offset] = i+src_offset;
      COPY_4FV(VB->CopyProj[i+dst_offset], proj[i+src_offset]);
   }

   return 0;
}






static void gl_fast_copy_noop( struct vertex_buffer *VB,
			       GLuint start, GLuint count,
			       GLuint ovf)
{
   (void) start;
   (void) ovf;
   (void) VB;
   (void) count;
}

static void gl_fast_copy_last( struct vertex_buffer *VB,
			       GLuint start, GLuint count,
			       GLuint ovf)
{
   (void) start;
   (void) ovf;
   VB->CopyCount = 1;
   VB->Copy[2] = count-1;
}

static void gl_fast_copy_first_and_last( struct vertex_buffer *VB, 
					 GLuint start, 
					 GLuint count,
					 GLuint ovf)
{
   (void) ovf;
   VB->CopyCount = 2;
   VB->Copy[1] = start;
   VB->Copy[2] = count-1;
}

static void gl_fast_copy_last_two( struct vertex_buffer *VB, 
				   GLuint start, GLuint count, 
				   GLuint ovf )
{
   (void) start;
   VB->CopyCount = 2;
   VB->Copy[1] = count-2;
   VB->Copy[2] = count-1;

   if (ovf == 1) {
      VB->CopyCount = 3;
      VB->Copy[0] = count-3;
   }
}

static void gl_fast_copy_overflow( struct vertex_buffer *VB, 
				   GLuint start, 
				   GLuint count,
				   GLuint ovf )
{
   GLuint src_offset = count - ovf;
   GLuint dst_offset = 3 - ovf;
   GLuint i;
   (void) start;
   VB->CopyCount = ovf;
   for (i = 0 ; i < ovf ; i++) 
      VB->Copy[i+dst_offset] = i+src_offset;
}


typedef void (*fast_copy_func)( struct vertex_buffer *VB, 
				GLuint start, 
				GLuint count,
				GLuint ovf );




typedef GLuint (*cull_func)( struct vertex_buffer *VB, 
			     GLuint start,
			     GLuint count,
			     GLuint parity,
			     CONST GLfloat (*proj)[4]);


typedef GLuint (*copy_func)( struct vertex_buffer *VB, 
			     GLuint start, 
			     GLuint end,
			     GLuint ovf,
			     CONST GLfloat (*proj)[4] );



static cull_func cull_tab_both[GL_POLYGON+2] = 
{
   gl_cull_points,
   gl_cull_lines,
   gl_cull_line_loop,
   gl_cull_line_strip,
   gl_cull_triangles,
   gl_cull_triangle_strip,
   gl_cull_triangle_fan,
   gl_cull_quads,
   gl_cull_quad_strip,
   gl_cull_triangle_fan,
   gl_cull_dummy_prim
};

static cull_func cull_tab_area[GL_POLYGON+2] = 
{
   gl_cull_points_neither,
   gl_cull_points_neither,
   gl_cull_points_neither,
   gl_cull_points_neither,
   gl_cull_triangles_area,
   gl_cull_triangle_strip_area,
   gl_cull_triangle_fan_area,
   gl_cull_quads_area,
   gl_cull_quad_strip_area,
   gl_cull_triangle_fan_area,
   gl_cull_dummy_prim
};

static cull_func cull_tab_clip[GL_POLYGON+2] = 
{
   gl_cull_points,
   gl_cull_lines,
   gl_cull_line_loop,
   gl_cull_line_strip,
   gl_cull_triangles_clip,
   gl_cull_triangle_strip_clip,
   gl_cull_triangle_fan_clip,
   gl_cull_quads_clip,
   gl_cull_quad_strip_clip,
   gl_cull_triangle_fan_clip,
   gl_cull_dummy_prim
};

static cull_func *cull_tab[4] = 
{
   0,
   cull_tab_area,
   cull_tab_clip,
   cull_tab_both
};


static copy_func copy_tab_cull[GL_POLYGON+2] = 
{
   0,
   gl_copy_overflow_cull,
   gl_copy_first_and_last_cull,
   gl_copy_last_cull,
   gl_copy_overflow_cull,
   gl_copy_last_two_cull,
   gl_copy_first_and_last_cull,
   gl_copy_overflow_cull,
   gl_copy_last_two_cull,
   gl_copy_first_and_last_cull,
   0,
};


static copy_func copy_tab_no_cull[GL_POLYGON+2] = 
{
   0,
   gl_copy_overflow,
   gl_copy_first_and_last,
   gl_copy_last,
   gl_copy_overflow,
   gl_copy_last_two,
   gl_copy_first_and_last,
   gl_copy_overflow,
   gl_copy_last_two,
   gl_copy_first_and_last,
   0,
};


static fast_copy_func fast_copy_tab[GL_POLYGON+2] = 
{
   gl_fast_copy_noop,
   gl_fast_copy_overflow,
   gl_fast_copy_first_and_last,
   gl_fast_copy_last,
   gl_fast_copy_overflow,
   gl_fast_copy_last_two,
   gl_fast_copy_first_and_last,
   gl_fast_copy_overflow,
   gl_fast_copy_last_two,
   gl_fast_copy_first_and_last,
   gl_fast_copy_noop,
};





/* Do this after (?) primitive fixup on buffers containing unwanted
 * eval coords.  No early culling will be done on these vertices (it's
 * a degenerate case & not worth the code), and the vertices will be
 * rendered by a special 'indirect' case in gl_render_vb.  Otherwise,
 * the cullmask will force (some) transforms to skip these vertices.
 */
void gl_purge_vertices( struct vertex_buffer *VB )
{
   GLuint *flags = VB->Flag;
   GLubyte *cullmask = VB->CullMask;
   GLuint *indirect = VB->Indirect = VB->EltPtr->start;
   GLuint *in_prim = VB->Primitive;
   GLuint *out_prim = VB->IM->Primitive;
   GLuint *in_nextprim = VB->NextPrimitive;
   GLuint *out_nextprim = VB->IM->NextPrimitive;
   GLuint count = VB->Count;
   GLuint purge = VB->PurgeFlags;
   GLuint next, start;
   GLuint j;

   /*
    */
   for ( j = start = VB->CopyStart ; start < count ; start = next ) {
      GLuint startj = j;
      GLuint i;

      next = in_nextprim[start]; 
      out_prim[j] = in_prim[start];

      for ( i = start ; i < next ; i++) {
	 if (~(flags[i] & purge)) {
	    indirect[j] = i;
	    cullmask[i] = PRIM_CLIPPED;	/* nonzero */
	    j++;
	 }
	 
      }

      out_nextprim[startj] = j;
   }
   
   VB->Primitive = out_prim;
   VB->NextPrimitive = out_nextprim;
   VB->IndirectCount = j;
}

#define VERT_NOT_CLIPPED 0x80

static void build_clip_vert_bits( GLubyte *clipmask, const GLubyte *cullmask,
				  GLuint count )
{
   GLuint i = 0;
   for (;;)
      if (cullmask[++i]==0) {
	 clipmask[i] |= CLIP_CULLED_BIT;
	 if (&cullmask[i] == &cullmask[count]) return;
      }
}

GLuint gl_cull_vb( struct vertex_buffer *VB )
{
   const GLcontext *ctx = VB->ctx;
   GLuint i, next, prim, n;
   GLfloat (*proj)[4] = VB->Projected->data;
   GLuint *in_prim = VB->Primitive;
   GLuint *out_prim = VB->IM->Primitive;
   GLuint cullcount = 0;
   GLuint lastprim = in_prim[VB->LastPrimitive];
   GLuint first = VB->CopyStart;
   GLuint parity = VB->Parity;
   cull_func *cull_funcs;
   GLuint idx = 0;

   if (VB->CullDone)
      return 0;

   if (VB->ClipOrMask)
      idx |= 0x2;

   if (ctx->IndirectTriangles & DD_ANY_CULL)
      idx |= 0x1;
   
   cull_funcs = cull_tab[idx];

   for (i = VB->CopyStart ; i < VB->Start ; i++) {
      COPY_4FV(proj[i], VB->CopyProj[i]);
   }

   VB->CopyCount = 0;

   MEMSET(VB->CullMask, 0, (VB->Count+1) * sizeof(GLubyte));

   for ( i = VB->CopyStart ; i < VB->Count ; i = next, parity = 0 ) 
   {
      first = i;
      next = VB->NextPrimitive[i];
      prim = in_prim[i];
      n = cull_funcs[prim]( VB, i, next, parity, (const GLfloat (*)[4])proj );

      if (n == next - i)
	 out_prim[i] = GL_POLYGON+1; 
      else 
	 out_prim[i] = prim;

      cullcount += n;
   }


   if (VB->LastPrimitive <  VB->Count) {
      if (copy_tab_cull[lastprim]) 
	 cullcount -= copy_tab_cull[prim]( VB, 
					   first, 
					   VB->Count, 
					   VB->Ovf, 
					   (const GLfloat (*)[4])proj );     
   }

   VB->Primitive = out_prim;
   
   VB->CullFlag[0] = VB->CullFlag[1] = (GLubyte) 0;

   if (cullcount > 0 || (ctx->IndirectTriangles & DD_LIGHTING_CULL)) {
      VB->CullMode |= CULL_MASK_ACTIVE;
      VB->CullFlag[0] = VB->CullFlag[1] = CLIP_CULLED_BIT&ctx->AllowVertexCull;

      if (cullcount < VB->Count)
	 build_clip_vert_bits( VB->ClipMask, VB->CullMask, VB->Count );

   }
   if (VB->ClipOrMask) {
      VB->CullMode |= CLIP_MASK_ACTIVE;
      VB->CullFlag[1] |= (CLIP_ALL_BITS|CLIP_USER_BIT) & ctx->AllowVertexCull; 
   }

   VB->CullDone = 1;
   return cullcount;
}


/* Need to set up to copy some vertices at the end of the buffer, even
 * if culling is disabled or the entire vb has been culled by clipping.
 */
void gl_dont_cull_vb( struct vertex_buffer *VB )
{
   GLfloat (*proj)[4] = VB->Projected->data;
   GLuint i;

   if (VB->CullDone)
      return;

   for (i = VB->CopyStart ; i < VB->Start ; i++) 
      COPY_4FV(proj[i], VB->CopyProj[i]);
   
   VB->CopyCount = 0;

   if (VB->LastPrimitive < VB->Count) 
   {
      GLuint first = VB->LastPrimitive;
      GLuint prim = VB->Primitive[first];

      if (first == VB_START)
	 first = VB->Start;

      if (copy_tab_no_cull[prim]) 
	 copy_tab_no_cull[prim]( VB, 
				 first, 
				 VB->Count, 
				 VB->Ovf,
				 (const GLfloat (*)[4])proj );
   }

   VB->CullDone = 1;
}

void gl_fast_copy_vb( struct vertex_buffer *VB )
{
   VB->CopyCount = 0;

   if (VB->LastPrimitive < VB->Count) 
   {
      GLuint first = VB->LastPrimitive;
      GLuint prim = VB->Primitive[first];

      if (first == VB_START) 
	 first = VB->Start;

      fast_copy_tab[prim]( VB, first, VB->Count, VB->Ovf );
   }

   VB->CullDone = 1;
}





void gl_make_normal_cullmask( struct vertex_buffer *VB )
{
   GLuint i;

   if (VB->CullMode & COMPACTED_NORMALS) {
      /* The shared normals case - never for cva.
       */
      MEMSET(VB->NormCullMask, 0, VB->Count * sizeof(GLubyte));      
      VB->NormCullStart = VB->NormCullMask + VB->Start;

      if (VB->CullMode & CULL_MASK_ACTIVE) {
	 GLubyte *lastnorm = VB->NormCullStart;

	 for (i = VB->Start ;;) {
	    *lastnorm |= VB->CullMask[i];
	    if (VB->Flag[++i] & (VERT_NORM|VERT_END_VB)) {
	       lastnorm = &VB->NormCullMask[i];
	       if (VB->Flag[i] & VERT_END_VB) return;
	    }
	 }
      } else {
	 VB->NormCullMask[VB->Start] = ~0;
	 for (i = VB->Start ;; ) 
	    if (VB->Flag[++i] & (VERT_NORM|VERT_END_VB)) {
	       VB->NormCullMask[i] = ~0;
	       if (VB->Flag[i] & VERT_END_VB) return;
	    }
      }
   } else  {
      /*  Non-shared normals.
       */
      VB->NormCullStart = VB->CullMask + VB->Start;      
   }   
}




