/* 
 *
 * GLX Hardware Device Driver for Sun Creator/Creator3D
 * Copyright (C) 2000, 2001 David S. Miller
 *
 * 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
 * DAVID MILLER, OR ANY OTHER CONTRIBUTORS 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.
 *
 *
 *    David S. Miller <davem@redhat.com>
 */

#include "main/glheader.h"
#include "main/mtypes.h"
#include "main/macros.h"
#include "swrast/swrast.h"
#include "swrast/s_context.h"
#include "swrast_setup/swrast_setup.h"
#include "tnl/t_context.h"
#include "tnl/t_pipeline.h"

#include "ffb_context.h"
#include "ffb_tris.h"
#include "ffb_lines.h"
#include "ffb_lock.h"
#include "ffb_points.h"
#include "ffb_state.h"
#include "ffb_vb.h"

#undef TRI_DEBUG
#undef FFB_RENDER_TRACE
#undef STATE_TRACE

#ifdef TRI_DEBUG
static void ffb_print_vertex(const ffb_vertex *v)
{
	fprintf(stderr, "Vertex @(%p): "
		"X[%f] Y[%f] Z[%f]\n",
		v, v->x, v->y, v->z);
	fprintf(stderr, "Vertex @(%p): "
		"A[%f] R[%f] G[%f] B[%f]\n",
		v,
		v->color[0].alpha,
		v->color[0].red,
		v->color[0].green,
		v->color[0].blue);
}
#define FFB_DUMP_VERTEX(V)	ffb_print_vertex(V)
#else
#define FFB_DUMP_VERTEX(V)	do { } while(0)
#endif

#define FFB_ALPHA_BIT		0x01
#define FFB_FLAT_BIT		0x02
#define FFB_TRI_CULL_BIT	0x04
#define MAX_FFB_RENDER_FUNCS	0x08

/***********************************************************************
 *         Build low-level triangle/quad rasterize functions           *
 ***********************************************************************/

#define FFB_TRI_FLAT_BIT	0x01
#define FFB_TRI_ALPHA_BIT	0x02
/*#define FFB_TRI_CULL_BIT	0x04*/

static ffb_tri_func ffb_tri_tab[0x8];
static ffb_quad_func ffb_quad_tab[0x8];

#define IND (0)
#define TAG(x) x
#include "ffb_tritmp.h"

#define IND (FFB_TRI_FLAT_BIT)
#define TAG(x) x##_flat
#include "ffb_tritmp.h"

#define IND (FFB_TRI_CULL_BIT)
#define TAG(x) x##_cull
#include "ffb_tritmp.h"

#define IND (FFB_TRI_CULL_BIT|FFB_TRI_FLAT_BIT)
#define TAG(x) x##_cull_flat
#include "ffb_tritmp.h"

#define IND (FFB_TRI_ALPHA_BIT)
#define TAG(x) x##_alpha
#include "ffb_tritmp.h"

#define IND (FFB_TRI_ALPHA_BIT|FFB_TRI_FLAT_BIT)
#define TAG(x) x##_alpha_flat
#include "ffb_tritmp.h"

#define IND (FFB_TRI_ALPHA_BIT|FFB_TRI_CULL_BIT)
#define TAG(x) x##_alpha_cull
#include "ffb_tritmp.h"

#define IND (FFB_TRI_ALPHA_BIT|FFB_TRI_CULL_BIT|FFB_TRI_FLAT_BIT)
#define TAG(x) x##_alpha_cull_flat
#include "ffb_tritmp.h"

static void init_tri_tab(void)
{
	ffb_init();
	ffb_init_flat();
	ffb_init_cull();
	ffb_init_cull_flat();
	ffb_init_alpha();
	ffb_init_alpha_flat();
	ffb_init_alpha_cull();
	ffb_init_alpha_cull_flat();
}

/* Build a SWvertex from a hardware vertex. */
static void ffb_translate_vertex(GLcontext *ctx, const ffb_vertex *src,
				 SWvertex *dst)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	GLfloat *m = ctx->Viewport._WindowMap.m;
	const GLfloat sx = m[0];
	const GLfloat sy = m[5];
	const GLfloat sz = m[10];
	const GLfloat tx = m[12];
	const GLfloat ty = m[13];
	const GLfloat tz = m[14];

	dst->attrib[FRAG_ATTRIB_WPOS][0] = sx * src->x + tx;
	dst->attrib[FRAG_ATTRIB_WPOS][1] = sy * src->y + ty;
	dst->attrib[FRAG_ATTRIB_WPOS][2] = sz * src->z + tz;
	dst->attrib[FRAG_ATTRIB_WPOS][3] = 1.0;
      
	dst->color[0] = FFB_UBYTE_FROM_COLOR(src->color[0].red);
	dst->color[1] = FFB_UBYTE_FROM_COLOR(src->color[0].green);
	dst->color[2] = FFB_UBYTE_FROM_COLOR(src->color[0].blue);
	dst->color[3] = FFB_UBYTE_FROM_COLOR(src->color[0].alpha);
}

/***********************************************************************
 *          Build fallback triangle/quad rasterize functions           *
 ***********************************************************************/

static void ffb_fallback_triangle(GLcontext *ctx, ffb_vertex *v0,
				  ffb_vertex *v1, ffb_vertex *v2)
{
	SWvertex v[3];

	ffb_translate_vertex(ctx, v0, &v[0]);
	ffb_translate_vertex(ctx, v1, &v[1]);
	ffb_translate_vertex(ctx, v2, &v[2]);

	_swrast_Triangle(ctx, &v[0], &v[1], &v[2]);
}

static void ffb_fallback_quad(GLcontext *ctx,
			      ffb_vertex *v0, ffb_vertex *v1, 
			      ffb_vertex *v2, ffb_vertex *v3)
{
	SWvertex v[4];

	ffb_translate_vertex(ctx, v0, &v[0]);
	ffb_translate_vertex(ctx, v1, &v[1]);
	ffb_translate_vertex(ctx, v2, &v[2]);
	ffb_translate_vertex(ctx, v3, &v[3]);

	_swrast_Quad(ctx, &v[0], &v[1], &v[2], &v[3]);
}

void ffb_fallback_line(GLcontext *ctx, ffb_vertex *v0, ffb_vertex *v1)
{
	SWvertex v[2];

	ffb_translate_vertex(ctx, v0, &v[0]);
	ffb_translate_vertex(ctx, v1, &v[1]);

	_swrast_Line(ctx, &v[0], &v[1]);
}

void ffb_fallback_point(GLcontext *ctx, ffb_vertex *v0)
{
	SWvertex v[1];

	ffb_translate_vertex(ctx, v0, &v[0]);

	_swrast_Point(ctx, &v[0]);
}

/***********************************************************************
 *             Rasterization functions for culled tris/quads           *
 ***********************************************************************/

static void ffb_nodraw_triangle(GLcontext *ctx, ffb_vertex *v0,
				ffb_vertex *v1, ffb_vertex *v2)
{
	(void) (ctx && v0 && v1 && v2);
}

static void ffb_nodraw_quad(GLcontext *ctx,
			    ffb_vertex *v0, ffb_vertex *v1, 
			    ffb_vertex *v2, ffb_vertex *v3)
{
	(void) (ctx && v0 && v1 && v2 && v3);
}

static void ffb_update_cullsign(GLcontext *ctx)
{
	GLfloat backface_sign = 1;

	switch (ctx->Polygon.CullFaceMode) {
	case GL_BACK:
		if (ctx->Polygon.FrontFace==GL_CCW)
			backface_sign = -1;
		break;

	case GL_FRONT:
		if (ctx->Polygon.FrontFace!=GL_CCW)
			backface_sign = -1;
		break;

	default:
		break;
	};

	FFB_CONTEXT(ctx)->backface_sign = backface_sign;
}

/***********************************************************************
 *               Choose triangle/quad rasterize functions              *
 ***********************************************************************/

void ffbChooseTriangleState(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	GLuint flags = ctx->_TriangleCaps;
	GLuint ind = 0;

	if (flags & DD_TRI_SMOOTH) {
		fmesa->draw_tri = ffb_fallback_triangle;
		fmesa->draw_quad = ffb_fallback_quad;
		return;
	}

	if (flags & DD_FLATSHADE)
		ind |= FFB_TRI_FLAT_BIT;

	if (ctx->Polygon.CullFlag) {
		if (ctx->Polygon.CullFaceMode == GL_FRONT_AND_BACK) {
			fmesa->draw_tri = ffb_nodraw_triangle;
			fmesa->draw_quad = ffb_nodraw_quad;
			return;
		}

		ind |= FFB_TRI_CULL_BIT;
		ffb_update_cullsign(ctx);
	} else
		FFB_CONTEXT(ctx)->backface_sign = 0;
		
	/* If blending or the alpha test is enabled we need to
	 * provide alpha components to the chip, else we can
	 * do without it and thus feed vertex data to the chip
	 * more efficiently.
	 */
	if (ctx->Color.BlendEnabled || ctx->Color.AlphaEnabled)
		ind |= FFB_TRI_ALPHA_BIT;

	fmesa->draw_tri = ffb_tri_tab[ind];
	fmesa->draw_quad = ffb_quad_tab[ind];
}

static const GLenum reduced_prim[GL_POLYGON+1] = {
   GL_POINTS,
   GL_LINES,
   GL_LINES,
   GL_LINES,
   GL_TRIANGLES,
   GL_TRIANGLES,
   GL_TRIANGLES,
   GL_TRIANGLES,
   GL_TRIANGLES,
   GL_TRIANGLES
};

static void ffbRenderPrimitive(GLcontext *ctx, GLenum prim);
static void ffbRasterPrimitive(GLcontext *ctx, GLenum rprim);

/***********************************************************************
 *              Build render functions from dd templates               *
 ***********************************************************************/

#define FFB_OFFSET_BIT  	0x01
#define FFB_TWOSIDE_BIT 	0x02
#define FFB_UNFILLED_BIT	0x04
#define FFB_MAX_TRIFUNC 	0x08

static struct {
	tnl_triangle_func triangle;
	tnl_quad_func quad;
} rast_tab[FFB_MAX_TRIFUNC];

#define DO_OFFSET   (IND & FFB_OFFSET_BIT)
#define DO_UNFILLED (IND & FFB_UNFILLED_BIT)
#define DO_TWOSIDE  (IND & FFB_TWOSIDE_BIT)
#define DO_FLAT      0
#define DO_QUAD      1
#define DO_FULL_QUAD 1
#define DO_TRI       1
#define DO_LINE      0
#define DO_POINTS    0

#define QUAD( a, b, c, d ) fmesa->draw_quad( ctx, a, b, c, d )
#define TRI( a, b, c )     fmesa->draw_tri( ctx, a, b, c )
#define LINE( a, b )       fmesa->draw_line( ctx, a, b )
#define POINT( a )         fmesa->draw_point( ctx, a )

#define HAVE_BACK_COLORS  1
#define HAVE_RGBA         1
#define HAVE_SPEC         0
#define HAVE_HW_FLATSHADE 1
#define VERTEX            ffb_vertex
#define TAB               rast_tab

#define UNFILLED_TRI      unfilled_tri
#define UNFILLED_QUAD     unfilled_quad
#define DEPTH_SCALE       (fmesa->depth_scale)
#define VERT_X(_v)        (_v->x)
#define VERT_Y(_v)        (_v->y)
#define VERT_Z(_v)	  (_v->z)
#define AREA_IS_CCW( a )  (a < fmesa->ffb_zero)
#define GET_VERTEX(e)     (&fmesa->verts[e])
#define INSANE_VERTICES
#define VERT_SET_Z(v,val) ((v)->z = (val))
#define VERT_Z_ADD(v,val) ((v)->z += (val))

#define VERT_COPY_RGBA1( _v )     _v->color[0] = _v->color[1]
#define VERT_COPY_RGBA( v0, v1 )  v0->color[0] = v1->color[0] 
#define VERT_SAVE_RGBA( idx )     color[idx] = v[idx]->color[0]
#define VERT_RESTORE_RGBA( idx )  v[idx]->color[0] = color[idx]   

#define LOCAL_VARS(n)				\
   ffbContextPtr fmesa = FFB_CONTEXT(ctx);	\
   __DRIdrawablePrivate *dPriv = fmesa->driDrawable; \
   ffb_color color[n];				\
   (void) color; (void) dPriv;

/***********************************************************************
 *                Helpers for rendering unfilled primitives            *
 ***********************************************************************/

#define RASTERIZE(x) if (fmesa->raster_primitive != reduced_prim[x]) \
                        ffbRasterPrimitive( ctx, reduced_prim[x] )
#define RENDER_PRIMITIVE fmesa->render_primitive
#define TAG(x) x
#include "tnl_dd/t_dd_unfilled.h"

/***********************************************************************
 *                      Generate GL render functions                   *
 ***********************************************************************/

#define IND (0)
#define TAG(x) x
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_OFFSET_BIT)
#define TAG(x) x##_offset
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_TWOSIDE_BIT)
#define TAG(x) x##_twoside
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_TWOSIDE_BIT|FFB_OFFSET_BIT)
#define TAG(x) x##_twoside_offset
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_UNFILLED_BIT)
#define TAG(x) x##_unfilled
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_OFFSET_BIT|FFB_UNFILLED_BIT)
#define TAG(x) x##_offset_unfilled
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_TWOSIDE_BIT|FFB_UNFILLED_BIT)
#define TAG(x) x##_twoside_unfilled
#include "tnl_dd/t_dd_tritmp.h"

#define IND (FFB_TWOSIDE_BIT|FFB_OFFSET_BIT|FFB_UNFILLED_BIT)
#define TAG(x) x##_twoside_offset_unfilled
#include "tnl_dd/t_dd_tritmp.h"

static void init_rast_tab( void )
{
   init();
   init_offset();
   init_twoside();
   init_twoside_offset();
   init_unfilled();
   init_offset_unfilled();
   init_twoside_unfilled();
   init_twoside_offset_unfilled();
}

/**********************************************************************/
/*                   Render clipped primitives                        */
/**********************************************************************/

static void ffbRenderClippedPolygon(GLcontext *ctx, const GLuint *elts, GLuint n)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	TNLcontext *tnl = TNL_CONTEXT(ctx);
	struct vertex_buffer *VB = &tnl->vb;
	GLuint prim = fmesa->render_primitive;

	/* Render the new vertices as an unclipped polygon. */
	{
		GLuint *tmp = VB->Elts;
		VB->Elts = (GLuint *)elts;
		tnl->Driver.Render.PrimTabElts[GL_POLYGON](ctx, 0, n, PRIM_BEGIN|PRIM_END);
		VB->Elts = tmp;
	}

	/* Restore the render primitive. */
	if (prim != GL_POLYGON)
		tnl->Driver.Render.PrimitiveNotify(ctx, prim);
}

static void ffbRenderClippedLine(GLcontext *ctx, GLuint ii, GLuint jj)
{
	TNLcontext *tnl = TNL_CONTEXT(ctx);
	tnl->Driver.Render.Line(ctx, ii, jj);
}

/**********************************************************************/
/*               Render unclipped begin/end objects                   */
/**********************************************************************/

static void ffb_vb_noop(GLcontext *ctx, GLuint start, GLuint count, GLuint flags)
{
	(void)(ctx && start && count && flags);
}

#define ELT(x)	x

#define IND	0
#define TAG(x)	x
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT)
#define TAG(x)	x##_flat
#include "ffb_rendertmp.h"

#define IND	(FFB_ALPHA_BIT)
#define TAG(x)	x##_alpha
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT | FFB_ALPHA_BIT)
#define TAG(x)	x##_flat_alpha
#include "ffb_rendertmp.h"

#define IND	(FFB_TRI_CULL_BIT)
#define TAG(x)	x##_tricull
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT | FFB_TRI_CULL_BIT)
#define TAG(x)	x##_flat_tricull
#include "ffb_rendertmp.h"

#define IND	(FFB_ALPHA_BIT | FFB_TRI_CULL_BIT)
#define TAG(x)	x##_alpha_tricull
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT | FFB_ALPHA_BIT | FFB_TRI_CULL_BIT)
#define TAG(x)	x##_flat_alpha_tricull
#include "ffb_rendertmp.h"

#undef ELT
#define ELT(x)	elt[x]

#define IND	0
#define TAG(x)	x##_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT)
#define TAG(x)	x##_flat_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_ALPHA_BIT)
#define TAG(x)	x##_alpha_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT | FFB_ALPHA_BIT)
#define TAG(x)	x##_flat_alpha_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_TRI_CULL_BIT)
#define TAG(x)	x##_tricull_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT | FFB_TRI_CULL_BIT)
#define TAG(x)	x##_flat_tricull_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_ALPHA_BIT | FFB_TRI_CULL_BIT)
#define TAG(x)	x##_alpha_tricull_elt
#include "ffb_rendertmp.h"

#define IND	(FFB_FLAT_BIT | FFB_ALPHA_BIT | FFB_TRI_CULL_BIT)
#define TAG(x)	x##_flat_alpha_tricull_elt
#include "ffb_rendertmp.h"

static void *render_tabs[MAX_FFB_RENDER_FUNCS];
static void *render_tabs_elt[MAX_FFB_RENDER_FUNCS];

static void init_render_tab(void)
{
	int i;

	render_tabs[0] = render_tab;
	render_tabs[FFB_FLAT_BIT] = render_tab_flat;
	render_tabs[FFB_ALPHA_BIT] = render_tab_alpha;
	render_tabs[FFB_FLAT_BIT|FFB_ALPHA_BIT] = render_tab_flat_alpha;
	render_tabs[FFB_TRI_CULL_BIT] = render_tab_tricull;
	render_tabs[FFB_FLAT_BIT|FFB_TRI_CULL_BIT] = render_tab_flat_tricull;
	render_tabs[FFB_ALPHA_BIT|FFB_TRI_CULL_BIT] = render_tab_alpha_tricull;
	render_tabs[FFB_FLAT_BIT|FFB_ALPHA_BIT|FFB_TRI_CULL_BIT] =
		render_tab_flat_alpha_tricull;

	render_tabs_elt[0] = render_tab_elt;
	render_tabs_elt[FFB_FLAT_BIT] = render_tab_flat_elt;
	render_tabs_elt[FFB_ALPHA_BIT] = render_tab_alpha_elt;
	render_tabs_elt[FFB_FLAT_BIT|FFB_ALPHA_BIT] = render_tab_flat_alpha_elt;
	render_tabs_elt[FFB_TRI_CULL_BIT] = render_tab_tricull_elt;
	render_tabs_elt[FFB_FLAT_BIT|FFB_TRI_CULL_BIT] = render_tab_flat_tricull_elt;
	render_tabs_elt[FFB_ALPHA_BIT|FFB_TRI_CULL_BIT] = render_tab_alpha_tricull_elt;
	render_tabs_elt[FFB_FLAT_BIT|FFB_ALPHA_BIT|FFB_TRI_CULL_BIT] =
		render_tab_flat_alpha_tricull_elt;

	for (i = 0; i < MAX_FFB_RENDER_FUNCS; i++) {
		tnl_render_func *rf = render_tabs[i];
		tnl_render_func *rfe = render_tabs_elt[i];

		if (i & FFB_TRI_CULL_BIT) {
			int from_idx = (i & ~FFB_TRI_CULL_BIT);
			tnl_render_func *rf_from = render_tabs[from_idx];
			tnl_render_func *rfe_from = render_tabs_elt[from_idx];
			int j;

			for (j = GL_POINTS; j < GL_TRIANGLES; j++) {
				rf[j] = rf_from[j];
				rfe[j] = rfe_from[j];
			}
		}
	}
}

/**********************************************************************/
/*                    Choose render functions                         */
/**********************************************************************/

#ifdef FFB_RENDER_TRACE
static void ffbPrintRenderFlags(GLuint index, GLuint render_index)
{
	fprintf(stderr,
		"ffbChooseRenderState: "
		"index(%s%s%s) "
		"render_index(%s%s%s)\n",
		((index & FFB_TWOSIDE_BIT) ? "twoside " : ""),
		((index & FFB_OFFSET_BIT) ? "offset " : ""),
		((index & FFB_UNFILLED_BIT) ? "unfilled " : ""),
		((render_index & FFB_FLAT_BIT) ? "flat " : ""),
		((render_index & FFB_ALPHA_BIT) ? "alpha " : ""),
		((render_index & FFB_TRI_CULL_BIT) ? "tricull " : ""));
}
#endif

void ffbChooseRenderState(GLcontext *ctx)
{
	GLuint flags = ctx->_TriangleCaps;
 	TNLcontext *tnl = TNL_CONTEXT(ctx);
	GLuint index = 0;

	/* Per-primitive fallbacks and the selection of fmesa->draw_* are
	 * handled elsewhere.
	 */
	if (flags & DD_TRI_LIGHT_TWOSIDE)       
		index |= FFB_TWOSIDE_BIT;

	if (flags & DD_TRI_OFFSET)	      
		index |= FFB_OFFSET_BIT;

	if (flags & DD_TRI_UNFILLED)	     
		index |= FFB_UNFILLED_BIT;

	tnl->Driver.Render.Triangle = rast_tab[index].triangle;
	tnl->Driver.Render.Quad = rast_tab[index].quad;

	if (index == 0) {
		GLuint render_index = 0;

		if (flags & DD_FLATSHADE)
			render_index |= FFB_FLAT_BIT;

		if (ctx->Color.BlendEnabled || ctx->Color.AlphaEnabled)
			render_index |= FFB_ALPHA_BIT;

		if (ctx->Polygon.CullFlag)
			render_index |= FFB_TRI_CULL_BIT;

#ifdef FFB_RENDER_TRACE
		ffbPrintRenderFlags(index, render_index);
#endif
		tnl->Driver.Render.PrimTabVerts = render_tabs[render_index];
		tnl->Driver.Render.PrimTabElts = render_tabs_elt[render_index];
	} else {
#ifdef FFB_RENDER_TRACE
		ffbPrintRenderFlags(index, 0);
#endif
		tnl->Driver.Render.PrimTabVerts = _tnl_render_tab_verts;
		tnl->Driver.Render.PrimTabElts = _tnl_render_tab_elts;
	}

	tnl->Driver.Render.ClippedPolygon = ffbRenderClippedPolygon;
	tnl->Driver.Render.ClippedLine    = ffbRenderClippedLine;
}

static void ffbRunPipeline(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	if (fmesa->bad_fragment_attrs == 0 &&
	    fmesa->new_gl_state) {
		if (fmesa->new_gl_state & _FFB_NEW_TRIANGLE)
			ffbChooseTriangleState(ctx);
		if (fmesa->new_gl_state & _FFB_NEW_LINE)
			ffbChooseLineState(ctx);
		if (fmesa->new_gl_state & _FFB_NEW_POINT)
			ffbChoosePointState(ctx);
		if (fmesa->new_gl_state & _FFB_NEW_RENDER)
			ffbChooseRenderState(ctx);
		if (fmesa->new_gl_state & _FFB_NEW_VERTEX)
			ffbChooseVertexState(ctx);

		fmesa->new_gl_state = 0;
	}

	_tnl_run_pipeline(ctx);
}

static void ffbRenderStart(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	LOCK_HARDWARE(fmesa);
	fmesa->hw_locked = 1;

	if (fmesa->state_dirty != 0)
		ffbSyncHardware(fmesa);
}

static void ffbRenderFinish(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	UNLOCK_HARDWARE(fmesa);
	fmesa->hw_locked = 0;
}

/* Even when doing full software rendering we need to
 * wrap render{start,finish} so that the hardware is kept
 * in sync (because multipass rendering changes the write
 * buffer etc.)
 */
static void ffbSWRenderStart(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	LOCK_HARDWARE(fmesa);
	fmesa->hw_locked = 1;

	if (fmesa->state_dirty != 0)
		ffbSyncHardware(fmesa);
}

static void ffbSWRenderFinish(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	UNLOCK_HARDWARE(fmesa);
	fmesa->hw_locked = 0;
}

static void ffbRasterPrimitive(GLcontext *ctx, GLenum rprim)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	GLuint drawop, fbc, ppc;
	int do_sw = 0;

	fmesa->raster_primitive = rprim;

	drawop = fmesa->drawop;
	fbc = fmesa->fbc;
	ppc = fmesa->ppc & ~(FFB_PPC_ZS_MASK | FFB_PPC_CS_MASK);

#ifdef STATE_TRACE
	fprintf(stderr,
		"ffbReducedPrimitiveChange: rprim(%d) ", rprim);
#endif
	switch(rprim) {
	case GL_POINTS:
#ifdef STATE_TRACE
		fprintf(stderr, "GL_POINTS ");
#endif
		if (fmesa->draw_point == ffb_fallback_point) {
			do_sw = 1;
			break;
		}

		if (ctx->Point.SmoothFlag) {
			ppc |= (FFB_PPC_ZS_VAR | FFB_PPC_CS_CONST);
			drawop = FFB_DRAWOP_AADOT;
		} else {
			ppc |= (FFB_PPC_ZS_CONST | FFB_PPC_CS_CONST);
			drawop = FFB_DRAWOP_DOT;
		}
		break;

	case GL_LINES:
#ifdef STATE_TRACE
		fprintf(stderr, "GL_LINES ");
#endif
		if (fmesa->draw_line == ffb_fallback_line) {
			do_sw = 1;
			break;
		}

		if (ctx->_TriangleCaps & DD_FLATSHADE) {
			ppc |= FFB_PPC_ZS_VAR | FFB_PPC_CS_CONST;
		} else {
			ppc |= FFB_PPC_ZS_VAR | FFB_PPC_CS_VAR;
		}
		if (ctx->Line.SmoothFlag)
			drawop = FFB_DRAWOP_AALINE;
		else
			drawop = FFB_DRAWOP_DDLINE;
		break;

	case GL_TRIANGLES:
#ifdef STATE_TRACE
		fprintf(stderr, "GL_POLYGON ");
#endif
		if (fmesa->draw_tri == ffb_fallback_triangle) {
			do_sw = 1;
			break;
		}

		ppc &= ~FFB_PPC_APE_MASK;
		if (ctx->Polygon.StippleFlag)
			ppc |= FFB_PPC_APE_ENABLE;
		else
			ppc |= FFB_PPC_APE_DISABLE;

		if (ctx->_TriangleCaps & DD_FLATSHADE) {
			ppc |= FFB_PPC_ZS_VAR | FFB_PPC_CS_CONST;
		} else {
			ppc |= FFB_PPC_ZS_VAR | FFB_PPC_CS_VAR;
		}
		drawop = FFB_DRAWOP_TRIANGLE;
		break;

	default:
#ifdef STATE_TRACE
		fprintf(stderr, "unknown %d!\n", rprim);
#endif
		return;
	};

#ifdef STATE_TRACE
	fprintf(stderr, "do_sw(%d) ", do_sw);
#endif
	if (do_sw != 0) {
		fbc &= ~(FFB_FBC_WB_C);
		fbc &= ~(FFB_FBC_ZE_MASK | FFB_FBC_RGBE_MASK);
		fbc |=   FFB_FBC_ZE_OFF  | FFB_FBC_RGBE_MASK;
		ppc &= ~(FFB_PPC_XS_MASK | FFB_PPC_ABE_MASK |
			 FFB_PPC_DCE_MASK | FFB_PPC_APE_MASK);
		ppc |=  (FFB_PPC_ZS_VAR | FFB_PPC_CS_VAR | FFB_PPC_XS_WID |
			 FFB_PPC_ABE_DISABLE | FFB_PPC_DCE_DISABLE |
			 FFB_PPC_APE_DISABLE);
	} else {
		fbc |= FFB_FBC_WB_C;
		fbc &= ~(FFB_FBC_RGBE_MASK);
		fbc |=   FFB_FBC_RGBE_MASK;
		ppc &= ~(FFB_PPC_ABE_MASK | FFB_PPC_XS_MASK);
		if (ctx->Color.BlendEnabled) {
			if ((rprim == GL_POINTS && !ctx->Point.SmoothFlag) ||
			    (rprim != GL_POINTS && ctx->_TriangleCaps & DD_FLATSHADE))
				ppc |= FFB_PPC_ABE_ENABLE | FFB_PPC_XS_CONST;
			else
				ppc |= FFB_PPC_ABE_ENABLE | FFB_PPC_XS_VAR;
		} else {
			ppc |= FFB_PPC_ABE_DISABLE | FFB_PPC_XS_WID;
		}
	}
#ifdef STATE_TRACE
	fprintf(stderr, "fbc(%08x) ppc(%08x)\n", fbc, ppc);
#endif

	FFBFifo(fmesa, 4);
	if (fmesa->drawop != drawop)
		fmesa->regs->drawop = fmesa->drawop = drawop;
	if (fmesa->fbc != fbc)
		fmesa->regs->fbc = fmesa->fbc = fbc;
	if (fmesa->ppc != ppc)
		fmesa->regs->ppc = fmesa->ppc = ppc;
	if (do_sw != 0) {
		fmesa->regs->cmp =
			(fmesa->cmp & ~(0xff<<16)) | (0x80 << 16);
	} else
		fmesa->regs->cmp = fmesa->cmp;
}

static void ffbRenderPrimitive(GLcontext *ctx, GLenum prim)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	GLuint rprim = reduced_prim[prim];

	fmesa->render_primitive = prim;

	if (rprim == GL_TRIANGLES && (ctx->_TriangleCaps & DD_TRI_UNFILLED))
		return;

	if (fmesa->raster_primitive != rprim) {
		ffbRasterPrimitive( ctx, rprim );
	}
}




/**********************************************************************/
/*           Transition to/from hardware rasterization.               */
/**********************************************************************/

static char *fallbackStrings[] = {
	"Fog enabled",
	"Blend function",
	"Blend ROP",
	"Blend equation",
	"Stencil",
	"Texture",
	"LIBGL_SOFTWARE_RENDERING"
};

static char *getFallbackString(GLuint bit)
{
	int i = 0;

	while (bit > 1) {
		i++;
		bit >>= 1;
	}
	return fallbackStrings[i];
}

void ffbFallback( GLcontext *ctx, GLuint bit, GLboolean mode )
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
 	TNLcontext *tnl = TNL_CONTEXT(ctx);
	GLuint oldfallback = fmesa->bad_fragment_attrs;

	if (mode) {
		fmesa->bad_fragment_attrs |= bit;
		if (oldfallback == 0) {
/*  			FFB_FIREVERTICES(fmesa); */
  		        _swsetup_Wakeup( ctx );
			if (fmesa->debugFallbacks)
				fprintf(stderr, "FFB begin software fallback: 0x%x %s\n",
					bit, getFallbackString(bit));
		}
	} else {
		fmesa->bad_fragment_attrs &= ~bit;
		if (oldfallback == bit) {
			_swrast_flush( ctx );

			tnl->Driver.Render.Start = ffbRenderStart;
			tnl->Driver.Render.PrimitiveNotify = ffbRenderPrimitive;
			tnl->Driver.Render.Finish = ffbRenderFinish;
			fmesa->new_gl_state = ~0;

			/* Just re-choose everything:
			 */
			ffbChooseVertexState(ctx);
			ffbChooseRenderState(ctx);
			ffbChooseTriangleState(ctx);
			ffbChooseLineState(ctx);
			ffbChoosePointState(ctx);

			if (fmesa->debugFallbacks)
				fprintf(stderr, "FFB end software fallback: 0x%x %s\n",
					bit, getFallbackString(bit));
		}
	}
}

/**********************************************************************/
/*                            Initialization.                         */
/**********************************************************************/

void ffbDDInitRenderFuncs( GLcontext *ctx )
{
 	TNLcontext *tnl = TNL_CONTEXT(ctx);
	SWcontext *swrast = SWRAST_CONTEXT(ctx);
	static int firsttime = 1;

	if (firsttime) {
		init_rast_tab();
		init_tri_tab();
		init_render_tab();
		firsttime = 0;
	}

	tnl->Driver.RunPipeline = ffbRunPipeline;
	tnl->Driver.Render.Start = ffbRenderStart;
	tnl->Driver.Render.Finish = ffbRenderFinish; 
	tnl->Driver.Render.PrimitiveNotify = ffbRenderPrimitive;
	tnl->Driver.Render.ResetLineStipple = _swrast_ResetLineStipple;
	tnl->Driver.Render.PrimTabVerts = _tnl_render_tab_verts;
	tnl->Driver.Render.PrimTabElts = _tnl_render_tab_elts;

	swrast->Driver.SpanRenderStart = ffbSWRenderStart;
	swrast->Driver.SpanRenderFinish = ffbSWRenderFinish;
}
