#include "shader/nvvertprog.h"
#include "shader/arbvertparse.h"
+struct state_key {
+ unsigned light_global_enabled:1;
+ unsigned light_local_viewer:1;
+ unsigned light_twoside:1;
+ unsigned light_color_material:1;
+ unsigned light_color_material_mask:12;
+ unsigned light_material_mask:12;
+
+ unsigned normalize:1;
+ unsigned rescale_normals:1;
+ unsigned fog_source_is_depth:1;
+ unsigned tnl_do_vertex_fog:1;
+ unsigned separate_specular:1;
+ unsigned fog_enabled:1;
+ unsigned fog_mode:2;
+ unsigned point_attenuated:1;
+ unsigned nr_tex:4;
+ unsigned texgen_enabled_global:1;
+
+ struct {
+ unsigned light_enabled:1;
+ unsigned light_eyepos3_is_zero:1;
+ unsigned light_spotcutoff_is_180:1;
+ unsigned light_attenuated:1;
+ unsigned texunit_really_enabled:1;
+ unsigned texmat_enabled:1;
+ unsigned texgen_enabled:4;
+ unsigned texgen_mode0:4;
+ unsigned texgen_mode1:4;
+ unsigned texgen_mode2:4;
+ unsigned texgen_mode3:4;
+ } unit[8];
+};
+
+
+
+#define FOG_LINEAR 0
+#define FOG_EXP 1
+#define FOG_EXP2 2
+#define FOG_UNKNOWN 3
+
+static GLuint translate_fog_mode( GLenum mode )
+{
+ switch (mode) {
+ case GL_LINEAR: return FOG_LINEAR;
+ case GL_EXP: return FOG_EXP;
+ case GL_EXP2: return FOG_EXP2;
+ default: return FOG_UNKNOWN;
+ }
+}
+
+#define TXG_NONE 0
+#define TXG_OBJ_LINEAR 1
+#define TXG_EYE_LINEAR 2
+#define TXG_SPHERE_MAP 3
+#define TXG_REFLECTION_MAP 4
+#define TXG_NORMAL_MAP 5
+
+static GLuint translate_texgen( GLboolean enabled, GLenum mode )
+{
+ if (!enabled)
+ return TXG_NONE;
+
+ switch (mode) {
+ case GL_OBJECT_LINEAR: return TXG_OBJ_LINEAR;
+ case GL_EYE_LINEAR: return TXG_EYE_LINEAR;
+ case GL_SPHERE_MAP: return TXG_SPHERE_MAP;
+ case GL_REFLECTION_MAP_NV: return TXG_REFLECTION_MAP;
+ case GL_NORMAL_MAP_NV: return TXG_NORMAL_MAP;
+ default: return TXG_NONE;
+ }
+}
+
+static struct state_key *make_state_key( GLcontext *ctx )
+{
+ TNLcontext *tnl = TNL_CONTEXT(ctx);
+ struct vertex_buffer *VB = &tnl->vb;
+ struct state_key *key = CALLOC_STRUCT(state_key);
+ GLuint i;
+
+ key->separate_specular = (ctx->Light.Model.ColorControl ==
+ GL_SEPARATE_SPECULAR_COLOR);
+
+ if (ctx->Light.Enabled) {
+ key->light_global_enabled = 1;
+
+ if (ctx->Light.Model.LocalViewer)
+ key->light_local_viewer = 1;
+
+ if (ctx->Light.Model.TwoSide)
+ key->light_twoside = 1;
+
+ if (ctx->Light.ColorMaterialEnabled)
+ key->light_color_material = 1;
+
+ if (ctx->Light.ColorMaterialBitmask)
+ key->light_color_material_mask = 1;
+
+ for (i = _TNL_ATTRIB_MAT_FRONT_AMBIENT ; i < _TNL_ATTRIB_INDEX ; i++)
+ if (VB->AttribPtr[i]->stride)
+ key->light_material_mask |= 1<<i;
+
+ for (i = 0; i < MAX_LIGHTS; i++) {
+ struct gl_light *light = &ctx->Light.Light[i];
+
+ if (light->Enabled) {
+ key->unit[i].light_enabled = 1;
+
+ if (light->EyePosition[3] == 0.0)
+ key->unit[i].light_eyepos3_is_zero = 1;
+
+ if (light->SpotCutoff == 180.0)
+ key->unit[i].light_spotcutoff_is_180 = 1;
+
+ if (light->ConstantAttenuation != 1.0 ||
+ light->LinearAttenuation != 1.0 ||
+ light->QuadraticAttenuation != 1.0)
+ key->unit[i].light_attenuated = 1;
+ }
+ }
+ }
+
+ if (ctx->Transform.Normalize)
+ key->normalize = 1;
+ if (ctx->Transform.RescaleNormals)
+ key->rescale_normals = 1;
+
+ if (ctx->Fog.Enabled)
+ key->fog_enabled = 1;
+
+ if (key->fog_enabled) {
+ if (ctx->Fog.FogCoordinateSource == GL_FRAGMENT_DEPTH_EXT)
+ key->fog_source_is_depth = 1;
+
+ if (tnl->_DoVertexFog)
+ key->tnl_do_vertex_fog = 1;
+
+ key->fog_mode = translate_fog_mode(ctx->Fog.Mode);
+ }
+
+ if (ctx->Point._Attenuated)
+ key->point_attenuated = 1;
+
+ if (ctx->Texture._TexGenEnabled)
+ key->texgen_enabled_global = 1;
+
+ for (i = 0; i < MAX_TEXTURE_UNITS; i++) {
+ struct gl_texture_unit *texUnit = &ctx->Texture.Unit[i];
+
+ if (texUnit->_ReallyEnabled)
+ key->unit[i].texunit_really_enabled = 1;
+
+ if (ctx->Texture._TexMatEnabled & ENABLE_TEXMAT(i))
+ key->unit[i].texmat_enabled = 1;
+
+ if (texUnit->TexGenEnabled) {
+ key->unit[i].texgen_enabled = 1;
+
+ key->unit[i].texgen_mode0 =
+ translate_texgen( texUnit->TexGenEnabled & (1<<0),
+ texUnit->GenModeS );
+ key->unit[i].texgen_mode1 =
+ translate_texgen( texUnit->TexGenEnabled & (1<<1),
+ texUnit->GenModeT );
+ key->unit[i].texgen_mode2 =
+ translate_texgen( texUnit->TexGenEnabled & (1<<2),
+ texUnit->GenModeR );
+ key->unit[i].texgen_mode3 =
+ translate_texgen( texUnit->TexGenEnabled & (1<<3),
+ texUnit->GenModeQ );
+ }
+ }
+
+ return key;
+}
+
+
+
/* Very useful debugging tool - produces annotated listing of
* generated program with line/function references for each
* instruction back into this file:
* multiplications with DP4's or with MUL/MAD's? SSE works better
* with the latter, drivers may differ.
*/
-#define PREFER_DP4 1
+#define PREFER_DP4 0
#define MAX_INSN 200
struct tnl_program {
- GLcontext *ctx;
+ const struct state_key *state;
struct vertex_program *program;
GLuint temp_in_use;
struct vp_instruction *inst = &p->program->Instructions[nr];
if (p->program->Base.NumInstructions > MAX_INSN) {
- _mesa_problem(p->ctx, "Out of instructions in emit_op3fn\n");
+ _mesa_problem(0, "Out of instructions in emit_op3fn\n");
return;
}
/* Normalize/Rescale:
*/
- if (p->ctx->Transform.Normalize) {
+ if (p->state->normalize) {
emit_normalize_vec3( p, p->eye_normal, p->eye_normal );
}
- else if (p->ctx->Transform.RescaleNormals) {
+ else if (p->state->rescale_normals) {
struct ureg rescale = register_param2(p, STATE_INTERNAL,
STATE_NORMAL_SCALE);
static void set_material_flags( struct tnl_program *p )
{
- GLcontext *ctx = p->ctx;
- TNLcontext *tnl = TNL_CONTEXT(ctx);
- GLuint i;
-
p->color_materials = 0;
p->materials = 0;
- if (ctx->Light.ColorMaterialEnabled) {
+ if (p->state->light_color_material) {
p->materials =
- p->color_materials =
- ctx->Light.ColorMaterialBitmask << _TNL_ATTRIB_MAT_FRONT_AMBIENT;
+ p->color_materials = p->state->light_color_material_mask;
}
- for (i = _TNL_ATTRIB_MAT_FRONT_AMBIENT ; i < _TNL_ATTRIB_INDEX ; i++)
- if (tnl->vb.AttribPtr[i]->stride)
- p->materials |= 1<<i;
+ p->materials |= p->state->light_material_mask;
}
static struct ureg calculate_light_attenuation( struct tnl_program *p,
GLuint i,
- struct gl_light *light,
struct ureg VPpli,
struct ureg dist )
{
/* Calculate spot attenuation:
*/
- if (light->SpotCutoff != 180.0F) {
+ if (!p->state->unit[i].light_spotcutoff_is_180) {
struct ureg spot_dir = register_param3(p, STATE_LIGHT, i,
STATE_SPOT_DIRECTION);
struct ureg spot = get_temp(p);
/* Calculate distance attenuation:
*/
- if (light->ConstantAttenuation != 1.0 ||
- light->LinearAttenuation != 1.0 ||
- light->QuadraticAttenuation != 1.0) {
+ if (p->state->unit[i].light_attenuated) {
/* 1/d,d,d,1/d */
emit_op1(p, VP_OPCODE_RCP, dist, WRITEMASK_YZ, dist);
/* 1/dist-atten */
emit_op2(p, VP_OPCODE_DP3, dist, 0, attenuation, dist);
- if (light->SpotCutoff != 180.0F) {
+ if (!p->state->unit[i].light_spotcutoff_is_180) {
/* dist-atten */
emit_op1(p, VP_OPCODE_RCP, dist, 0, dist);
/* spot-atten * dist-atten */
*/
static void build_lighting( struct tnl_program *p )
{
- GLcontext *ctx = p->ctx;
- const GLboolean twoside = ctx->Light.Model.TwoSide;
- const GLboolean separate = (ctx->Light.Model.ColorControl ==
- GL_SEPARATE_SPECULAR_COLOR);
+ const GLboolean twoside = p->state->light_twoside;
+ const GLboolean separate = p->state->separate_specular;
GLuint nr_lights = 0, count = 0;
struct ureg normal = get_eye_normal(p);
struct ureg lit = get_temp(p);
GLuint i;
for (i = 0; i < MAX_LIGHTS; i++)
- if (ctx->Light.Light[i].Enabled)
+ if (p->state->unit[i].light_enabled)
nr_lights++;
set_material_flags(p);
for (i = 0; i < MAX_LIGHTS; i++) {
- struct gl_light *light = &ctx->Light.Light[i];
-
- if (light->Enabled) {
+ if (p->state->unit[i].light_enabled) {
struct ureg half = undef;
struct ureg att = undef, VPpli = undef;
count++;
- if (light->EyePosition[3] == 0) {
+ if (p->state->unit[i].light_eyepos3_is_zero) {
/* Can used precomputed constants in this case.
* Attenuation never applies to infinite lights.
*/
/* Calculate attenuation:
*/
- if (light->SpotCutoff != 180.0 ||
- light->ConstantAttenuation != 1.0 ||
- light->LinearAttenuation != 1.0 ||
- light->QuadraticAttenuation != 1.0) {
- att = calculate_light_attenuation(p, i, light, VPpli, dist);
+ if (!p->state->unit[i].light_spotcutoff_is_180 ||
+ p->state->unit[i].light_attenuated) {
+ att = calculate_light_attenuation(p, i, VPpli, dist);
}
/* Calculate viewer direction, or use infinite viewer:
*/
- if (ctx->Light.Model.LocalViewer) {
+ if (p->state->light_local_viewer) {
struct ureg eye_hat = get_eye_position_normalized(p);
emit_op2(p, VP_OPCODE_SUB, half, 0, VPpli, eye_hat);
}
static void build_fog( struct tnl_program *p )
{
- GLcontext *ctx = p->ctx;
- TNLcontext *tnl = TNL_CONTEXT(ctx);
struct ureg fog = register_output(p, VERT_RESULT_FOGC);
struct ureg input;
- if (ctx->Fog.FogCoordinateSource == GL_FRAGMENT_DEPTH_EXT) {
+ if (p->state->fog_source_is_depth) {
input = swizzle1(get_eye_position(p), Z);
}
else {
input = swizzle1(register_input(p, VERT_ATTRIB_FOG), X);
}
- if (tnl->_DoVertexFog) {
+ if (p->state->tnl_do_vertex_fog) {
struct ureg params = register_param1(p, STATE_FOG_PARAMS);
struct ureg tmp = get_temp(p);
- switch (ctx->Fog.Mode) {
- case GL_LINEAR: {
+ switch (p->state->fog_mode) {
+ case FOG_LINEAR: {
struct ureg id = get_identity_param(p);
emit_op2(p, VP_OPCODE_SUB, tmp, 0, swizzle1(params,Z), input);
emit_op2(p, VP_OPCODE_MUL, tmp, 0, tmp, swizzle1(params,W));
emit_op2(p, VP_OPCODE_MIN, fog, WRITEMASK_X, tmp, swizzle1(id,W));
break;
}
- case GL_EXP:
+ case FOG_EXP:
emit_op1(p, VP_OPCODE_ABS, tmp, 0, input);
emit_op2(p, VP_OPCODE_MUL, tmp, 0, tmp, swizzle1(params,X));
emit_op2(p, VP_OPCODE_POW, fog, WRITEMASK_X,
register_const1f(p, M_E), negate(tmp));
break;
- case GL_EXP2:
+ case FOG_EXP2:
emit_op2(p, VP_OPCODE_MUL, tmp, 0, input, swizzle1(params,X));
emit_op2(p, VP_OPCODE_MUL, tmp, 0, tmp, tmp);
emit_op2(p, VP_OPCODE_POW, fog, WRITEMASK_X,
static void build_texture_transform( struct tnl_program *p )
{
- GLcontext *ctx = p->ctx;
GLuint i, j;
- for (i = 0; i < ctx->Const.MaxTextureCoordUnits; i++) {
- struct gl_texture_unit *texUnit = &ctx->Texture.Unit[i];
- GLuint texmat_enabled = ctx->Texture._TexMatEnabled & ENABLE_TEXMAT(i);
+ for (i = 0; i < MAX_TEXTURE_UNITS; i++) {
+ GLuint texmat_enabled = p->state->unit[i].texmat_enabled;
- if (texUnit->TexGenEnabled || texmat_enabled) {
+ if (p->state->unit[i].texgen_enabled || texmat_enabled) {
struct ureg out = register_output(p, VERT_RESULT_TEX0 + i);
struct ureg out_texgen = undef;
- if (texUnit->TexGenEnabled) {
+ if (p->state->unit[i].texgen_enabled) {
GLuint copy_mask = 0;
GLuint sphere_mask = 0;
GLuint reflect_mask = 0;
else
out_texgen = out;
- modes[0] = texUnit->GenModeS;
- modes[1] = texUnit->GenModeT;
- modes[2] = texUnit->GenModeR;
- modes[3] = texUnit->GenModeQ;
+ modes[0] = p->state->unit[i].texgen_mode0;
+ modes[1] = p->state->unit[i].texgen_mode1;
+ modes[2] = p->state->unit[i].texgen_mode2;
+ modes[3] = p->state->unit[i].texgen_mode3;
for (j = 0; j < 4; j++) {
- if (texUnit->TexGenEnabled & (1<<j)) {
- switch (modes[j]) {
- case GL_OBJECT_LINEAR: {
- struct ureg obj = register_input(p, VERT_ATTRIB_POS);
- struct ureg plane =
- register_param3(p, STATE_TEXGEN, i,
- STATE_TEXGEN_OBJECT_S + j);
-
- emit_op2(p, VP_OPCODE_DP4, out_texgen, WRITEMASK_X << j,
- obj, plane );
- break;
- }
- case GL_EYE_LINEAR: {
- struct ureg eye = get_eye_position(p);
- struct ureg plane =
- register_param3(p, STATE_TEXGEN, i,
- STATE_TEXGEN_EYE_S + j);
-
- emit_op2(p, VP_OPCODE_DP4, out_texgen, WRITEMASK_X << j,
- eye, plane );
- break;
- }
- case GL_SPHERE_MAP:
- sphere_mask |= WRITEMASK_X << j;
- break;
- case GL_REFLECTION_MAP_NV:
- reflect_mask |= WRITEMASK_X << j;
- break;
- case GL_NORMAL_MAP_NV:
- normal_mask |= WRITEMASK_X << j;
- break;
- }
+ switch (modes[j]) {
+ case TXG_OBJ_LINEAR: {
+ struct ureg obj = register_input(p, VERT_ATTRIB_POS);
+ struct ureg plane =
+ register_param3(p, STATE_TEXGEN, i,
+ STATE_TEXGEN_OBJECT_S + j);
+
+ emit_op2(p, VP_OPCODE_DP4, out_texgen, WRITEMASK_X << j,
+ obj, plane );
+ break;
}
- else
+ case TXG_EYE_LINEAR: {
+ struct ureg eye = get_eye_position(p);
+ struct ureg plane =
+ register_param3(p, STATE_TEXGEN, i,
+ STATE_TEXGEN_EYE_S + j);
+
+ emit_op2(p, VP_OPCODE_DP4, out_texgen, WRITEMASK_X << j,
+ eye, plane );
+ break;
+ }
+ case TXG_SPHERE_MAP:
+ sphere_mask |= WRITEMASK_X << j;
+ break;
+ case TXG_REFLECTION_MAP:
+ reflect_mask |= WRITEMASK_X << j;
+ break;
+ case TXG_NORMAL_MAP:
+ normal_mask |= WRITEMASK_X << j;
+ break;
+ case TXG_NONE:
copy_mask |= WRITEMASK_X << j;
+ }
+
}
release_temps(p);
}
- else if (texUnit->_ReallyEnabled) {
+ else if (p->state->unit[i].texunit_really_enabled) {
/* KW: _ReallyEnabled isn't sufficient? Need to know whether
* this texture unit is referenced by the fragment shader.
*/
release_temp(p, ut);
}
+static void build_tnl_program( struct tnl_program *p )
+{ /* Emit the program, starting with modelviewproject:
+ */
+ build_hpos(p);
+
+ /* Lighting calculations:
+ */
+ if (p->state->light_global_enabled)
+ build_lighting(p);
+ else
+ emit_passthrough(p, VERT_ATTRIB_COLOR0, VERT_RESULT_COL0);
-static GLboolean programs_eq( struct vertex_program *a,
- struct vertex_program *b )
-{
- if (!a || !b)
- return GL_FALSE;
+ if (p->state->fog_enabled)
+ build_fog(p);
- if (a->Base.NumInstructions != b->Base.NumInstructions ||
- a->Parameters->NumParameters != b->Parameters->NumParameters)
- return GL_FALSE;
+ if (p->state->nr_tex)
+ build_texture_transform(p);
- if (memcmp(a->Instructions, b->Instructions,
- a->Base.NumInstructions * sizeof(struct vp_instruction)) != 0)
- return GL_FALSE;
+ if (p->state->point_attenuated)
+ build_pointsize(p);
- if (memcmp(a->Parameters->Parameters, b->Parameters->Parameters,
- a->Parameters->NumParameters *
- sizeof(struct program_parameter)) != 0)
- return GL_FALSE;
+ /* Finish up:
+ */
+ emit_op1(p, VP_OPCODE_END, undef, 0, undef);
- return GL_TRUE;
+ /* Disassemble:
+ */
+ if (DISASSEM) {
+ _mesa_printf ("\n");
+ }
}
-void _tnl_UpdateFixedFunctionProgram( GLcontext *ctx )
+void create_new_program( const struct state_key *key,
+ struct vertex_program *program,
+ GLuint max_temps)
{
struct tnl_program p;
- if (ctx->VertexProgram._Enabled)
- return;
-
-
_mesa_memset(&p, 0, sizeof(p));
- p.ctx = ctx;
- p.program = (struct vertex_program *)ctx->Driver.NewProgram(ctx, GL_VERTEX_PROGRAM_ARB, 0);
+ p.state = key;
+ p.program = program;
p.eye_position = undef;
p.eye_position_normalized = undef;
p.eye_normal = undef;
p.identity = undef;
-
p.temp_in_use = 0;
-
- if (ctx->Const.MaxVertexProgramTemps >= sizeof(int) * 8)
+
+ if (max_temps >= sizeof(int) * 8)
p.temp_reserved = 0;
else
- p.temp_reserved = ~((1<<ctx->Const.MaxVertexProgramTemps)-1);
+ p.temp_reserved = ~((1<<max_temps)-1);
p.program->Instructions = MALLOC(sizeof(struct vp_instruction) * MAX_INSN);
-
- /* Initialize the arb_program struct */
p.program->Base.String = 0;
p.program->Base.NumInstructions =
p.program->Base.NumTemporaries =
p.program->Base.NumParameters =
p.program->Base.NumAttributes = p.program->Base.NumAddressRegs = 0;
-
- if (p.program->Parameters)
- _mesa_free_parameters(p.program->Parameters);
- else
- p.program->Parameters = _mesa_new_parameter_list();
-
+ p.program->Parameters = _mesa_new_parameter_list();
p.program->InputsRead = 0;
p.program->OutputsWritten = 0;
- /* Emit the program, starting with modelviewproject:
- */
- build_hpos(&p);
+ build_tnl_program( &p );
+}
- /* Lighting calculations:
+static void *search_cache( struct tnl_cache *cache,
+ GLuint hash,
+ const void *key,
+ GLuint keysize)
+{
+ struct tnl_cache *c;
+
+ for (c = cache; c; c = c->next) {
+ if (c->hash == hash && memcmp(c->key, key, keysize) == 0)
+ return c->data;
+ }
+
+ return NULL;
+}
+
+static void cache_item( struct tnl_cache **cache,
+ GLuint hash,
+ void *key,
+ void *data )
+{
+ struct tnl_cache *c = MALLOC(sizeof(*c));
+ c->hash = hash;
+ c->key = key;
+ c->data = data;
+ c->next = *cache;
+ *cache = c;
+}
+
+static GLuint hash_key( struct state_key *key )
+{
+ GLuint *ikey = (GLuint *)key;
+ GLuint hash = 0, i;
+
+ /* I'm sure this can be improved on, but speed is important:
*/
- if (ctx->Light.Enabled)
- build_lighting(&p);
- else
- emit_passthrough(&p, VERT_ATTRIB_COLOR0, VERT_RESULT_COL0);
+ for (i = 0; i < sizeof(*key)/sizeof(GLuint); i++)
+ hash ^= ikey[i];
- if (ctx->Fog.Enabled)
- build_fog(&p);
+ return hash;
+}
- if (ctx->Texture._TexGenEnabled || ctx->Texture._TexMatEnabled)
- build_texture_transform(&p);
+void _tnl_UpdateFixedFunctionProgram( GLcontext *ctx )
+{
+ TNLcontext *tnl = TNL_CONTEXT(ctx);
+ struct state_key *key;
+ GLuint hash;
- if (ctx->Point._Attenuated)
- build_pointsize(&p);
+ if (ctx->VertexProgram._Enabled)
+ return;
- /* Finish up:
+ /* Grab all the relevent state and put it in a single structure:
*/
- emit_op1(&p, VP_OPCODE_END, undef, 0, undef);
+ key = make_state_key(ctx);
+ hash = hash_key(key);
- /* Disassemble:
+ /* Look for an already-prepared program for this state:
*/
- if (DISASSEM) {
- _mesa_printf ("\n");
- }
+ ctx->_TnlProgram = (struct vertex_program *)
+ search_cache( tnl->vp_cache, hash, key, sizeof(key) );
+
+ /* OK, we'll have to build a new one:
+ */
+ if (!ctx->_TnlProgram) {
+ if (0)
+ _mesa_printf("Build new TNL program\n");
+ ctx->_TnlProgram = (struct vertex_program *)
+ ctx->Driver.NewProgram(ctx, GL_VERTEX_PROGRAM_ARB, 0);
- /* Notify driver the fragment program has (actually) changed.
- */
- if (!programs_eq(ctx->_TnlProgram, p.program) != 0) {
- if (ctx->_TnlProgram)
- ctx->Driver.DeleteProgram( ctx, &ctx->_TnlProgram->Base );
- ctx->_TnlProgram = p.program;
+ create_new_program( key, ctx->_TnlProgram,
+ ctx->Const.MaxVertexProgramTemps );
+
+ cache_item(&tnl->vp_cache, hash, key, ctx->_TnlProgram );
}
- else if (p.program) {
- /* Nothing changed...
- */
- ctx->Driver.DeleteProgram( ctx, &p.program->Base );
+ else {
+ if (0)
+ _mesa_printf("Found existing TNL program for key %x\n", hash);
+ }
+
+ /* Need a BindProgram callback for the driver?
+ */
+}
+
+
+void _tnl_ProgramCacheDestroy( GLcontext *ctx )
+{
+ TNLcontext *tnl = TNL_CONTEXT(ctx);
+ struct tnl_cache *a, *tmp;
+
+ for (a = tnl->vp_cache ; a; a = tmp) {
+ tmp = a->next;
+ FREE(a->key);
+ FREE(a->data);
+ FREE(a);
}
}