Core of the future vertex engine. Isn't built yet, nor will be for a while...
[mesa.git] / src / mesa / tnl / t_vtx_exec.c
1 /* $XFree86$ */
2 /**************************************************************************
3
4 Copyright 2002 Tungsten Graphics Inc., Cedar Park, Texas.
5
6 All Rights Reserved.
7
8 Permission is hereby granted, free of charge, to any person obtaining a
9 copy of this software and associated documentation files (the "Software"),
10 to deal in the Software without restriction, including without limitation
11 on the rights to use, copy, modify, merge, publish, distribute, sub
12 license, and/or sell copies of the Software, and to permit persons to whom
13 the Software is furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice (including the next
16 paragraph) shall be included in all copies or substantial portions of the
17 Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
22 TUNGSTEN GRAPHICS AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
23 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
25 USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27 **************************************************************************/
28
29 /*
30 * Authors:
31 * Keith Whitwell <keith@tungstengraphics.com>
32 *
33 */
34 #include "api_noop.h"
35 #include "api_arrayelt.h"
36 #include "context.h"
37 #include "mem.h"
38 #include "mmath.h"
39 #include "mtypes.h"
40 #include "enums.h"
41 #include "glapi.h"
42 #include "colormac.h"
43 #include "light.h"
44 #include "state.h"
45 #include "vtxfmt.h"
46
47 #include "tnl/tnl.h"
48 #include "tnl/t_context.h"
49 #include "tnl/t_array_api.h"
50
51 static void _tnl_FlushVertices( GLcontext *, GLuint );
52
53
54 void tnl_copy_to_current( GLcontext *ctx )
55 {
56 TNLcontext *tnl = TNL_CONTEXT(ctx);
57 GLuint flag = tnl->vertex_format;
58 GLint i;
59
60 assert(ctx->Driver.NeedFlush & FLUSH_UPDATE_CURRENT);
61
62 for (i = 0 ; i < 16 ; i++)
63 if (flag & (1<<i))
64 COPY_4FV( ctx->Current.Attrib[i], tnl->attribptr[i] );
65
66 if (flag & VERT_BIT_INDEX)
67 ctx->Current.Index = tnl->indexptr[0];
68
69 if (flag & VERT_BIT_EDGEFLAG)
70 ctx->Current.EdgeFlag = tnl->edgeflagptr[0];
71
72 if (flag & VERT_BIT_MATERIAL) {
73 _mesa_update_material( ctx,
74 IM->Material[IM->LastMaterial],
75 IM->MaterialOrMask );
76
77 tnl->Driver.NotifyMaterialChange( ctx );
78 }
79
80
81 ctx->Driver.NeedFlush &= ~FLUSH_UPDATE_CURRENT;
82 }
83
84 static GLboolean discreet_gl_prim[GL_POLYGON+1] = {
85 1, /* 0 points */
86 1, /* 1 lines */
87 0, /* 2 line_strip */
88 0, /* 3 line_loop */
89 1, /* 4 tris */
90 0, /* 5 tri_fan */
91 0, /* 6 tri_strip */
92 1, /* 7 quads */
93 0, /* 8 quadstrip */
94 0, /* 9 poly */
95 };
96
97 /* Optimize the primitive list: ONLY FOR EXECUTE ATM
98 */
99 static void optimize_prims( TNLcontext *tnl )
100 {
101 int i, j;
102
103 if (tnl->nrprims <= 1)
104 return;
105
106 for (j = 0, i = 1 ; i < tnl->nrprims; i++) {
107 int pj = tnl->primlist[j].prim & 0xf;
108 int pi = tnl->primlist[i].prim & 0xf;
109
110 if (pj == pi && discreet_gl_prim[pj] &&
111 tnl->primlist[i].start == tnl->primlist[j].end) {
112 tnl->primlist[j].end = tnl->primlist[i].end;
113 }
114 else {
115 j++;
116 if (j != i) tnl->primlist[j] = tnl->primlist[i];
117 }
118 }
119
120 tnl->nrprims = j+1;
121 }
122
123
124 /* Bind vertex buffer pointers, run pipeline:
125 */
126 static void flush_prims( TNLcontext *tnl )
127 {
128 int i,j;
129
130 tnl->dma.current.ptr = tnl->dma.current.start +=
131 (tnl->initial_counter - tnl->counter) * tnl->vertex_size * 4;
132
133 tnl->tcl.vertex_format = tnl->vertex_format;
134 tnl->tcl.aos_components[0] = &tmp;
135 tnl->tcl.nr_aos_components = 1;
136 tnl->dma.flush = 0;
137
138 tnl->Driver.RunPipeline( ... );
139
140 tnl->nrprims = 0;
141 }
142
143
144 static void start_prim( TNLcontext *tnl, GLuint mode )
145 {
146 if (MESA_VERBOSE & DEBUG_VFMT)
147 fprintf(stderr, "%s %d\n", __FUNCTION__,
148 tnl->initial_counter - tnl->counter);
149
150 tnl->primlist[tnl->nrprims].start = tnl->initial_counter - tnl->counter;
151 tnl->primlist[tnl->nrprims].prim = mode;
152 }
153
154 static void note_last_prim( TNLcontext *tnl, GLuint flags )
155 {
156 if (MESA_VERBOSE & DEBUG_VFMT)
157 fprintf(stderr, "%s %d\n", __FUNCTION__,
158 tnl->initial_counter - tnl->counter);
159
160 if (tnl->prim[0] != GL_POLYGON+1) {
161 tnl->primlist[tnl->nrprims].prim |= flags;
162 tnl->primlist[tnl->nrprims].end = tnl->initial_counter - tnl->counter;
163
164 if (++tnl->nrprims == TNL_MAX_PRIMS)
165 flush_prims( tnl );
166 }
167 }
168
169
170 static void copy_vertex( TNLcontext *tnl, GLuint n, GLfloat *dst )
171 {
172 GLuint i;
173 GLfloat *src = (GLfloat *)(tnl->dma.current.address +
174 tnl->dma.current.ptr +
175 (tnl->primlist[tnl->nrprims].start + n) *
176 tnl->vertex_size * 4);
177
178 if (MESA_VERBOSE & DEBUG_VFMT)
179 fprintf(stderr, "copy_vertex %d\n",
180 tnl->primlist[tnl->nrprims].start + n);
181
182 for (i = 0 ; i < tnl->vertex_size; i++) {
183 dst[i] = src[i];
184 }
185 }
186
187 /* NOTE: This actually reads the copied vertices back from uncached
188 * memory. Could also use the counter/notify mechanism to populate
189 * tmp on the fly as vertices are generated.
190 */
191 static GLuint copy_wrapped_verts( TNLcontext *tnl, GLfloat (*tmp)[15] )
192 {
193 GLuint ovf, i;
194 GLuint nr = (tnl->initial_counter - tnl->counter) - tnl->primlist[tnl->nrprims].start;
195
196 if (MESA_VERBOSE & DEBUG_VFMT)
197 fprintf(stderr, "%s %d verts\n", __FUNCTION__, nr);
198
199 switch( tnl->prim[0] )
200 {
201 case GL_POINTS:
202 return 0;
203 case GL_LINES:
204 ovf = nr&1;
205 for (i = 0 ; i < ovf ; i++)
206 copy_vertex( tnl, nr-ovf+i, tmp[i] );
207 return i;
208 case GL_TRIANGLES:
209 ovf = nr%3;
210 for (i = 0 ; i < ovf ; i++)
211 copy_vertex( tnl, nr-ovf+i, tmp[i] );
212 return i;
213 case GL_QUADS:
214 ovf = nr&3;
215 for (i = 0 ; i < ovf ; i++)
216 copy_vertex( tnl, nr-ovf+i, tmp[i] );
217 return i;
218 case GL_LINE_STRIP:
219 if (nr == 0)
220 return 0;
221 copy_vertex( tnl, nr-1, tmp[0] );
222 return 1;
223 case GL_LINE_LOOP:
224 case GL_TRIANGLE_FAN:
225 case GL_POLYGON:
226 if (nr == 0)
227 return 0;
228 else if (nr == 1) {
229 copy_vertex( tnl, 0, tmp[0] );
230 return 1;
231 } else {
232 copy_vertex( tnl, 0, tmp[0] );
233 copy_vertex( tnl, nr-1, tmp[1] );
234 return 2;
235 }
236 case GL_TRIANGLE_STRIP:
237 ovf = MIN2( nr-1, 2 );
238 for (i = 0 ; i < ovf ; i++)
239 copy_vertex( tnl, nr-ovf+i, tmp[i] );
240 return i;
241 case GL_QUAD_STRIP:
242 ovf = MIN2( nr-1, 2 );
243 if (nr > 2) ovf += nr&1;
244 for (i = 0 ; i < ovf ; i++)
245 copy_vertex( tnl, nr-ovf+i, tmp[i] );
246 return i;
247 default:
248 assert(0);
249 return 0;
250 }
251 }
252
253
254
255 /* Extend for vertex-format changes on wrap:
256 */
257 static void wrap_buffer( void )
258 {
259 TNLcontext *tnl = tnl->tnl;
260 GLfloat tmp[3][15];
261 GLuint i, nrverts;
262
263 if (MESA_VERBOSE & (DEBUG_VFMT|DEBUG_PRIMS))
264 fprintf(stderr, "%s %d\n", __FUNCTION__,
265 tnl->initial_counter - tnl->counter);
266
267 /* Don't deal with parity. *** WONT WORK FOR COMPILE
268 */
269 if ((((tnl->initial_counter - tnl->counter) -
270 tnl->primlist[tnl->nrprims].start) & 1)) {
271 tnl->counter++;
272 tnl->initial_counter++;
273 return;
274 }
275
276 /* Copy vertices out of dma:
277 */
278 nrverts = copy_dma_verts( tnl, tmp );
279
280 if (MESA_VERBOSE & DEBUG_VFMT)
281 fprintf(stderr, "%d vertices to copy\n", nrverts);
282
283
284 /* Finish the prim at this point:
285 */
286 note_last_prim( tnl, 0 );
287 flush_prims( tnl );
288
289 /* Reset counter, dmaptr
290 */
291 tnl->dmaptr = (int *)(tnl->dma.current.ptr + tnl->dma.current.address);
292 tnl->counter = (tnl->dma.current.end - tnl->dma.current.ptr) /
293 (tnl->vertex_size * 4);
294 tnl->counter--;
295 tnl->initial_counter = tnl->counter;
296 tnl->notify = wrap_buffer;
297
298 tnl->dma.flush = flush_prims;
299 start_prim( tnl, tnl->prim[0] );
300
301
302 /* Reemit saved vertices
303 * *** POSSIBLY IN NEW FORMAT
304 * --> Can't always extend at end of vertex?
305 */
306 for (i = 0 ; i < nrverts; i++) {
307 if (MESA_VERBOSE & DEBUG_VERTS) {
308 int j;
309 fprintf(stderr, "re-emit vertex %d to %p\n", i, tnl->dmaptr);
310 if (MESA_VERBOSE & DEBUG_VERBOSE)
311 for (j = 0 ; j < tnl->vertex_size; j++)
312 fprintf(stderr, "\t%08x/%f\n", *(int*)&tmp[i][j], tmp[i][j]);
313 }
314
315 memcpy( tnl->dmaptr, tmp[i], tnl->vertex_size * 4 );
316 tnl->dmaptr += tnl->vertex_size;
317 tnl->counter--;
318 }
319 }
320
321
322
323 /* Always follow data, don't try to predict what's necessary.
324 */
325 static GLboolean check_vtx_fmt( GLcontext *ctx )
326 {
327 TNLcontext *tnl = TNL_CONTEXT(ctx);
328
329 if (ctx->Driver.NeedFlush & FLUSH_UPDATE_CURRENT)
330 ctx->Driver.FlushVertices( ctx, FLUSH_UPDATE_CURRENT );
331
332
333 TNL_NEWPRIM(tnl);
334 tnl->vertex_format = VERT_BIT_POS;
335 tnl->prim = &ctx->Driver.CurrentExecPrimitive;
336
337
338 /* Currently allow the full 4 components per attrib. Can use the
339 * mechanism from radeon driver color handling to reduce this (and
340 * also to store ubyte colors where these are incoming). This
341 * won't work for compile mode.
342 *
343 * Only adding components when they are first received eliminates
344 * the need for displaylist fixup, as there are no 'empty' slots
345 * at the start of buffers.
346 */
347 for (i = 0 ; i < 16 ; i++) {
348 if (ind & (1<<i)) {
349 tnl->attribptr[i] = &tnl->vertex[tnl->vertex_size].f;
350 tnl->vertex_size += 4;
351 tnl->attribptr[i][0] = ctx->Current.Attrib[i][0];
352 tnl->attribptr[i][1] = ctx->Current.Attrib[i][1];
353 tnl->attribptr[i][2] = ctx->Current.Attrib[i][2];
354 tnl->attribptr[i][3] = ctx->Current.Attrib[i][3];
355 }
356 else
357 tnl->attribptr[i] = ctx->Current.Attrib[i];
358 }
359
360 /* Edgeflag, Index:
361 */
362 for (i = 16 ; i < 18 ; i++)
363 ;
364
365 /* Materials:
366 */
367 for (i = 18 ; i < 28 ; i++)
368 ;
369
370 /* Eval:
371 */
372 for (i = 28 ; i < 29 ; i++)
373 ;
374
375
376 if (tnl->installed_vertex_format != tnl->vertex_format) {
377 if (MESA_VERBOSE & DEBUG_VFMT)
378 fprintf(stderr, "reinstall on vertex_format change\n");
379 _mesa_install_exec_vtxfmt( ctx, &tnl->vtxfmt );
380 tnl->installed_vertex_format = tnl->vertex_format;
381 }
382
383 return GL_TRUE;
384 }
385
386
387 void _tnl_InvalidateVtxfmt( GLcontext *ctx )
388 {
389 tnl->recheck = GL_TRUE;
390 tnl->fell_back = GL_FALSE;
391 }
392
393
394
395
396 static void _tnl_ValidateVtxfmt( GLcontext *ctx )
397 {
398 if (MESA_VERBOSE & DEBUG_VFMT)
399 fprintf(stderr, "%s\n", __FUNCTION__);
400
401 if (ctx->Driver.NeedFlush)
402 ctx->Driver.FlushVertices( ctx, ctx->Driver.NeedFlush );
403
404 tnl->recheck = GL_FALSE;
405
406 if (check_vtx_fmt( ctx )) {
407 if (!tnl->installed) {
408 if (MESA_VERBOSE & DEBUG_VFMT)
409 fprintf(stderr, "reinstall (new install)\n");
410
411 _mesa_install_exec_vtxfmt( ctx, &tnl->vtxfmt );
412 ctx->Driver.FlushVertices = _tnl_FlushVertices;
413 tnl->installed = GL_TRUE;
414 }
415 else
416 fprintf(stderr, "%s: already installed", __FUNCTION__);
417 }
418 else {
419 if (MESA_VERBOSE & DEBUG_VFMT)
420 fprintf(stderr, "%s: failed\n", __FUNCTION__);
421
422 if (tnl->installed) {
423 if (tnl->tnl->dma.flush)
424 tnl->tnl->dma.flush( tnl->tnl );
425 _tnl_wakeup_exec( ctx );
426 tnl->installed = GL_FALSE;
427 }
428 }
429 }
430
431
432
433
434
435 /* Begin/End
436 */
437 static void _tnl_Begin( GLenum mode )
438 {
439 GLcontext *ctx = tnl->context;
440 TNLcontext *tnl = tnl->tnl;
441
442 if (MESA_VERBOSE & DEBUG_VFMT)
443 fprintf(stderr, "%s\n", __FUNCTION__);
444
445 if (mode > GL_POLYGON) {
446 _mesa_error( ctx, GL_INVALID_ENUM, "glBegin" );
447 return;
448 }
449
450 if (tnl->prim[0] != GL_POLYGON+1) {
451 _mesa_error( ctx, GL_INVALID_OPERATION, "glBegin" );
452 return;
453 }
454
455 if (ctx->NewState)
456 _mesa_update_state( ctx );
457
458 if (tnl->recheck)
459 _tnl_ValidateVtxfmt( ctx );
460
461 if (tnl->dma.flush && tnl->counter < 12) {
462 if (MESA_VERBOSE & DEBUG_VFMT)
463 fprintf(stderr, "%s: flush almost-empty buffers\n", __FUNCTION__);
464 flush_prims( tnl );
465 }
466
467 if (!tnl->dma.flush) {
468 if (tnl->dma.current.ptr + 12*tnl->vertex_size*4 >
469 tnl->dma.current.end) {
470 TNL_NEWPRIM( tnl );
471 _tnl_RefillCurrentDmaRegion( tnl );
472 }
473
474 tnl->dmaptr = (int *)(tnl->dma.current.address + tnl->dma.current.ptr);
475 tnl->counter = (tnl->dma.current.end - tnl->dma.current.ptr) /
476 (tnl->vertex_size * 4);
477 tnl->counter--;
478 tnl->initial_counter = tnl->counter;
479 tnl->notify = wrap_buffer;
480 tnl->dma.flush = flush_prims;
481 tnl->context->Driver.NeedFlush |= FLUSH_STORED_VERTICES;
482 }
483
484
485 tnl->prim[0] = mode;
486 start_prim( tnl, mode | PRIM_BEGIN );
487 }
488
489
490
491
492
493 static void _tnl_End( void )
494 {
495 TNLcontext *tnl = tnl->tnl;
496 GLcontext *ctx = tnl->context;
497
498 if (MESA_VERBOSE & DEBUG_VFMT)
499 fprintf(stderr, "%s\n", __FUNCTION__);
500
501 if (tnl->prim[0] == GL_POLYGON+1) {
502 _mesa_error( ctx, GL_INVALID_OPERATION, "glEnd" );
503 return;
504 }
505
506 note_last_prim( tnl, PRIM_END );
507 tnl->prim[0] = GL_POLYGON+1;
508 }
509
510
511 static void _tnl_FlushVertices( GLcontext *ctx, GLuint flags )
512 {
513 if (MESA_VERBOSE & DEBUG_VFMT)
514 fprintf(stderr, "%s\n", __FUNCTION__);
515
516 assert(tnl->installed);
517
518 if (flags & FLUSH_UPDATE_CURRENT) {
519 _tnl_copy_to_current( ctx );
520 if (MESA_VERBOSE & DEBUG_VFMT)
521 fprintf(stderr, "reinstall on update_current\n");
522 _mesa_install_exec_vtxfmt( ctx, &tnl->vtxfmt );
523 ctx->Driver.NeedFlush &= ~FLUSH_UPDATE_CURRENT;
524 }
525
526 if (flags & FLUSH_STORED_VERTICES) {
527 TNLcontext *tnl = TNL_CONTEXT( ctx );
528 assert (tnl->dma.flush == 0 ||
529 tnl->dma.flush == flush_prims);
530 if (tnl->dma.flush == flush_prims)
531 flush_prims( TNL_CONTEXT( ctx ) );
532 ctx->Driver.NeedFlush &= ~FLUSH_STORED_VERTICES;
533 }
534 }
535
536
537
538 /* At this point, don't expect very many versions of each function to
539 * be generated, so not concerned about freeing them?
540 */
541
542
543 static void _tnl_InitVtxfmt( GLcontext *ctx )
544 {
545 GLvertexformat *vfmt = &(tnl->vtxfmt);
546
547 MEMSET( vfmt, 0, sizeof(GLvertexformat) );
548
549 /* Hook in chooser functions for codegen, etc:
550 */
551 _tnl_InitVtxfmtChoosers( vfmt );
552
553 /* Handled fully in supported states, but no codegen:
554 */
555 vfmt->ArrayElement = _ae_loopback_array_elt; /* generic helper */
556 vfmt->Rectf = _mesa_noop_Rectf; /* generic helper */
557 vfmt->Begin = _tnl_Begin;
558 vfmt->End = _tnl_End;
559
560 tnl->context = ctx;
561 tnl->tnl = TNL_CONTEXT(ctx);
562 tnl->prim = &ctx->Driver.CurrentExecPrimitive;
563 tnl->primflags = 0;
564
565 make_empty_list( &tnl->dfn_cache.Vertex2f );
566 make_empty_list( &tnl->dfn_cache.Vertex2fv );
567 make_empty_list( &tnl->dfn_cache.Vertex3f );
568 make_empty_list( &tnl->dfn_cache.Vertex3fv );
569 make_empty_list( &tnl->dfn_cache.Color4ub );
570 make_empty_list( &tnl->dfn_cache.Color4ubv );
571 make_empty_list( &tnl->dfn_cache.Color3ub );
572 make_empty_list( &tnl->dfn_cache.Color3ubv );
573 make_empty_list( &tnl->dfn_cache.Color4f );
574 make_empty_list( &tnl->dfn_cache.Color4fv );
575 make_empty_list( &tnl->dfn_cache.Color3f );
576 make_empty_list( &tnl->dfn_cache.Color3fv );
577 make_empty_list( &tnl->dfn_cache.SecondaryColor3fEXT );
578 make_empty_list( &tnl->dfn_cache.SecondaryColor3fvEXT );
579 make_empty_list( &tnl->dfn_cache.SecondaryColor3ubEXT );
580 make_empty_list( &tnl->dfn_cache.SecondaryColor3ubvEXT );
581 make_empty_list( &tnl->dfn_cache.Normal3f );
582 make_empty_list( &tnl->dfn_cache.Normal3fv );
583 make_empty_list( &tnl->dfn_cache.TexCoord2f );
584 make_empty_list( &tnl->dfn_cache.TexCoord2fv );
585 make_empty_list( &tnl->dfn_cache.TexCoord1f );
586 make_empty_list( &tnl->dfn_cache.TexCoord1fv );
587 make_empty_list( &tnl->dfn_cache.MultiTexCoord2fARB );
588 make_empty_list( &tnl->dfn_cache.MultiTexCoord2fvARB );
589 make_empty_list( &tnl->dfn_cache.MultiTexCoord1fARB );
590 make_empty_list( &tnl->dfn_cache.MultiTexCoord1fvARB );
591
592 _tnl_InitCodegen( &tnl->codegen );
593 }
594
595 static void free_funcs( struct dynfn *l )
596 {
597 struct dynfn *f, *tmp;
598 foreach_s (f, tmp, l) {
599 remove_from_list( f );
600 ALIGN_FREE( f->code );
601 FREE( f );
602 }
603 }
604
605
606 static void _tnl_DestroyVtxfmt( GLcontext *ctx )
607 {
608 count_funcs();
609 free_funcs( &tnl->dfn_cache.Vertex2f );
610 free_funcs( &tnl->dfn_cache.Vertex2fv );
611 free_funcs( &tnl->dfn_cache.Vertex3f );
612 free_funcs( &tnl->dfn_cache.Vertex3fv );
613 free_funcs( &tnl->dfn_cache.Color4ub );
614 free_funcs( &tnl->dfn_cache.Color4ubv );
615 free_funcs( &tnl->dfn_cache.Color3ub );
616 free_funcs( &tnl->dfn_cache.Color3ubv );
617 free_funcs( &tnl->dfn_cache.Color4f );
618 free_funcs( &tnl->dfn_cache.Color4fv );
619 free_funcs( &tnl->dfn_cache.Color3f );
620 free_funcs( &tnl->dfn_cache.Color3fv );
621 free_funcs( &tnl->dfn_cache.SecondaryColor3ubEXT );
622 free_funcs( &tnl->dfn_cache.SecondaryColor3ubvEXT );
623 free_funcs( &tnl->dfn_cache.SecondaryColor3fEXT );
624 free_funcs( &tnl->dfn_cache.SecondaryColor3fvEXT );
625 free_funcs( &tnl->dfn_cache.Normal3f );
626 free_funcs( &tnl->dfn_cache.Normal3fv );
627 free_funcs( &tnl->dfn_cache.TexCoord2f );
628 free_funcs( &tnl->dfn_cache.TexCoord2fv );
629 free_funcs( &tnl->dfn_cache.TexCoord1f );
630 free_funcs( &tnl->dfn_cache.TexCoord1fv );
631 free_funcs( &tnl->dfn_cache.MultiTexCoord2fARB );
632 free_funcs( &tnl->dfn_cache.MultiTexCoord2fvARB );
633 free_funcs( &tnl->dfn_cache.MultiTexCoord1fARB );
634 free_funcs( &tnl->dfn_cache.MultiTexCoord1fvARB );
635 }
636