[FMIFS]
[reactos.git] / reactos / dll / opengl / mesa / vbo / vbo_save_api.c
1 /**************************************************************************
2
3 Copyright 2002-2008 Tungsten Graphics Inc., Cedar Park, Texas.
4
5 All Rights Reserved.
6
7 Permission is hereby granted, free of charge, to any person obtaining a
8 copy of this software and associated documentation files (the "Software"),
9 to deal in the Software without restriction, including without limitation
10 on the rights to use, copy, modify, merge, publish, distribute, sub
11 license, and/or sell copies of the Software, and to permit persons to whom
12 the Software is furnished to do so, subject to the following conditions:
13
14 The above copyright notice and this permission notice (including the next
15 paragraph) shall be included in all copies or substantial portions of the
16 Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
21 TUNGSTEN GRAPHICS AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
22 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24 USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 **************************************************************************/
27
28 /*
29 * Authors:
30 * Keith Whitwell <keith@tungstengraphics.com>
31 */
32
33
34
35 /* Display list compiler attempts to store lists of vertices with the
36 * same vertex layout. Additionally it attempts to minimize the need
37 * for execute-time fixup of these vertex lists, allowing them to be
38 * cached on hardware.
39 *
40 * There are still some circumstances where this can be thwarted, for
41 * example by building a list that consists of one very long primitive
42 * (eg Begin(Triangles), 1000 vertices, End), and calling that list
43 * from inside a different begin/end object (Begin(Lines), CallList,
44 * End).
45 *
46 * In that case the code will have to replay the list as individual
47 * commands through the Exec dispatch table, or fix up the copied
48 * vertices at execute-time.
49 *
50 * The other case where fixup is required is when a vertex attribute
51 * is introduced in the middle of a primitive. Eg:
52 * Begin(Lines)
53 * TexCoord1f() Vertex2f()
54 * TexCoord1f() Color3f() Vertex2f()
55 * End()
56 *
57 * If the current value of Color isn't known at compile-time, this
58 * primitive will require fixup.
59 *
60 *
61 * The list compiler currently doesn't attempt to compile lists
62 * containing EvalCoord or EvalPoint commands. On encountering one of
63 * these, compilation falls back to opcodes.
64 *
65 * This could be improved to fallback only when a mix of EvalCoord and
66 * Vertex commands are issued within a single primitive.
67 */
68
69 #include <precomp.h>
70
71 #if FEATURE_dlist
72
73
74 #ifdef ERROR
75 #undef ERROR
76 #endif
77
78
79 /* An interesting VBO number/name to help with debugging */
80 #define VBO_BUF_ID 12345
81
82
83 /*
84 * NOTE: Old 'parity' issue is gone, but copying can still be
85 * wrong-footed on replay.
86 */
87 static GLuint
88 _save_copy_vertices(struct gl_context *ctx,
89 const struct vbo_save_vertex_list *node,
90 const GLfloat * src_buffer)
91 {
92 struct vbo_save_context *save = &vbo_context(ctx)->save;
93 const struct _mesa_prim *prim = &node->prim[node->prim_count - 1];
94 GLuint nr = prim->count;
95 GLuint sz = save->vertex_size;
96 const GLfloat *src = src_buffer + prim->start * sz;
97 GLfloat *dst = save->copied.buffer;
98 GLuint ovf, i;
99
100 if (prim->end)
101 return 0;
102
103 switch (prim->mode) {
104 case GL_POINTS:
105 return 0;
106 case GL_LINES:
107 ovf = nr & 1;
108 for (i = 0; i < ovf; i++)
109 memcpy(dst + i * sz, src + (nr - ovf + i) * sz,
110 sz * sizeof(GLfloat));
111 return i;
112 case GL_TRIANGLES:
113 ovf = nr % 3;
114 for (i = 0; i < ovf; i++)
115 memcpy(dst + i * sz, src + (nr - ovf + i) * sz,
116 sz * sizeof(GLfloat));
117 return i;
118 case GL_QUADS:
119 ovf = nr & 3;
120 for (i = 0; i < ovf; i++)
121 memcpy(dst + i * sz, src + (nr - ovf + i) * sz,
122 sz * sizeof(GLfloat));
123 return i;
124 case GL_LINE_STRIP:
125 if (nr == 0)
126 return 0;
127 else {
128 memcpy(dst, src + (nr - 1) * sz, sz * sizeof(GLfloat));
129 return 1;
130 }
131 case GL_LINE_LOOP:
132 case GL_TRIANGLE_FAN:
133 case GL_POLYGON:
134 if (nr == 0)
135 return 0;
136 else if (nr == 1) {
137 memcpy(dst, src + 0, sz * sizeof(GLfloat));
138 return 1;
139 }
140 else {
141 memcpy(dst, src + 0, sz * sizeof(GLfloat));
142 memcpy(dst + sz, src + (nr - 1) * sz, sz * sizeof(GLfloat));
143 return 2;
144 }
145 case GL_TRIANGLE_STRIP:
146 case GL_QUAD_STRIP:
147 switch (nr) {
148 case 0:
149 ovf = 0;
150 break;
151 case 1:
152 ovf = 1;
153 break;
154 default:
155 ovf = 2 + (nr & 1);
156 break;
157 }
158 for (i = 0; i < ovf; i++)
159 memcpy(dst + i * sz, src + (nr - ovf + i) * sz,
160 sz * sizeof(GLfloat));
161 return i;
162 default:
163 assert(0);
164 return 0;
165 }
166 }
167
168
169 static struct vbo_save_vertex_store *
170 alloc_vertex_store(struct gl_context *ctx)
171 {
172 struct vbo_save_context *save = &vbo_context(ctx)->save;
173 struct vbo_save_vertex_store *vertex_store =
174 CALLOC_STRUCT(vbo_save_vertex_store);
175
176 /* obj->Name needs to be non-zero, but won't ever be examined more
177 * closely than that. In particular these buffers won't be entered
178 * into the hash and can never be confused with ones visible to the
179 * user. Perhaps there could be a special number for internal
180 * buffers:
181 */
182 vertex_store->bufferobj = ctx->Driver.NewBufferObject(ctx,
183 VBO_BUF_ID,
184 GL_ARRAY_BUFFER_ARB);
185 if (vertex_store->bufferobj) {
186 save->out_of_memory =
187 !ctx->Driver.BufferData(ctx,
188 GL_ARRAY_BUFFER_ARB,
189 VBO_SAVE_BUFFER_SIZE * sizeof(GLfloat),
190 NULL, GL_STATIC_DRAW_ARB,
191 vertex_store->bufferobj);
192 }
193 else {
194 save->out_of_memory = GL_TRUE;
195 }
196
197 if (save->out_of_memory) {
198 _mesa_error(ctx, GL_OUT_OF_MEMORY, "internal VBO allocation");
199 _mesa_install_save_vtxfmt(ctx, &save->vtxfmt_noop);
200 }
201
202 vertex_store->buffer = NULL;
203 vertex_store->used = 0;
204 vertex_store->refcount = 1;
205
206 return vertex_store;
207 }
208
209
210 static void
211 free_vertex_store(struct gl_context *ctx,
212 struct vbo_save_vertex_store *vertex_store)
213 {
214 assert(!vertex_store->buffer);
215
216 if (vertex_store->bufferobj) {
217 _mesa_reference_buffer_object(ctx, &vertex_store->bufferobj, NULL);
218 }
219
220 FREE(vertex_store);
221 }
222
223
224 static GLfloat *
225 map_vertex_store(struct gl_context *ctx,
226 struct vbo_save_vertex_store *vertex_store)
227 {
228 assert(vertex_store->bufferobj);
229 assert(!vertex_store->buffer);
230 if (vertex_store->bufferobj->Size > 0) {
231 vertex_store->buffer =
232 (GLfloat *) ctx->Driver.MapBufferRange(ctx, 0,
233 vertex_store->bufferobj->Size,
234 GL_MAP_WRITE_BIT, /* not used */
235 vertex_store->bufferobj);
236 assert(vertex_store->buffer);
237 return vertex_store->buffer + vertex_store->used;
238 }
239 else {
240 /* probably ran out of memory for buffers */
241 return NULL;
242 }
243 }
244
245
246 static void
247 unmap_vertex_store(struct gl_context *ctx,
248 struct vbo_save_vertex_store *vertex_store)
249 {
250 if (vertex_store->bufferobj->Size > 0) {
251 ctx->Driver.UnmapBuffer(ctx, vertex_store->bufferobj);
252 }
253 vertex_store->buffer = NULL;
254 }
255
256
257 static struct vbo_save_primitive_store *
258 alloc_prim_store(struct gl_context *ctx)
259 {
260 struct vbo_save_primitive_store *store =
261 CALLOC_STRUCT(vbo_save_primitive_store);
262 (void) ctx;
263 store->used = 0;
264 store->refcount = 1;
265 return store;
266 }
267
268
269 static void
270 _save_reset_counters(struct gl_context *ctx)
271 {
272 struct vbo_save_context *save = &vbo_context(ctx)->save;
273
274 save->prim = save->prim_store->buffer + save->prim_store->used;
275 save->buffer = save->vertex_store->buffer + save->vertex_store->used;
276
277 assert(save->buffer == save->buffer_ptr);
278
279 if (save->vertex_size)
280 save->max_vert = ((VBO_SAVE_BUFFER_SIZE - save->vertex_store->used) /
281 save->vertex_size);
282 else
283 save->max_vert = 0;
284
285 save->vert_count = 0;
286 save->prim_count = 0;
287 save->prim_max = VBO_SAVE_PRIM_SIZE - save->prim_store->used;
288 save->dangling_attr_ref = 0;
289 }
290
291
292 /**
293 * Insert the active immediate struct onto the display list currently
294 * being built.
295 */
296 static void
297 _save_compile_vertex_list(struct gl_context *ctx)
298 {
299 struct vbo_save_context *save = &vbo_context(ctx)->save;
300 struct vbo_save_vertex_list *node;
301
302 /* Allocate space for this structure in the display list currently
303 * being compiled.
304 */
305 node = (struct vbo_save_vertex_list *)
306 _mesa_dlist_alloc(ctx, save->opcode_vertex_list, sizeof(*node));
307
308 if (!node)
309 return;
310
311 /* Duplicate our template, increment refcounts to the storage structs:
312 */
313 memcpy(node->attrsz, save->attrsz, sizeof(node->attrsz));
314 node->vertex_size = save->vertex_size;
315 node->buffer_offset =
316 (save->buffer - save->vertex_store->buffer) * sizeof(GLfloat);
317 node->count = save->vert_count;
318 node->wrap_count = save->copied.nr;
319 node->dangling_attr_ref = save->dangling_attr_ref;
320 node->prim = save->prim;
321 node->prim_count = save->prim_count;
322 node->vertex_store = save->vertex_store;
323 node->prim_store = save->prim_store;
324
325 node->vertex_store->refcount++;
326 node->prim_store->refcount++;
327
328 if (node->prim[0].no_current_update) {
329 node->current_size = 0;
330 node->current_data = NULL;
331 }
332 else {
333 node->current_size = node->vertex_size - node->attrsz[0];
334 node->current_data = NULL;
335
336 if (node->current_size) {
337 /* If the malloc fails, we just pull the data out of the VBO
338 * later instead.
339 */
340 node->current_data = MALLOC(node->current_size * sizeof(GLfloat));
341 if (node->current_data) {
342 const char *buffer = (const char *) save->vertex_store->buffer;
343 unsigned attr_offset = node->attrsz[0] * sizeof(GLfloat);
344 unsigned vertex_offset = 0;
345
346 if (node->count)
347 vertex_offset =
348 (node->count - 1) * node->vertex_size * sizeof(GLfloat);
349
350 memcpy(node->current_data,
351 buffer + node->buffer_offset + vertex_offset + attr_offset,
352 node->current_size * sizeof(GLfloat));
353 }
354 }
355 }
356
357 assert(node->attrsz[VBO_ATTRIB_POS] != 0 || node->count == 0);
358
359 if (save->dangling_attr_ref)
360 ctx->ListState.CurrentList->Flags |= DLIST_DANGLING_REFS;
361
362 save->vertex_store->used += save->vertex_size * node->count;
363 save->prim_store->used += node->prim_count;
364
365 /* Copy duplicated vertices
366 */
367 save->copied.nr = _save_copy_vertices(ctx, node, save->buffer);
368
369 /* Deal with GL_COMPILE_AND_EXECUTE:
370 */
371 if (ctx->ExecuteFlag) {
372 struct _glapi_table *dispatch = GET_DISPATCH();
373
374 _glapi_set_dispatch(ctx->Exec);
375
376 vbo_loopback_vertex_list(ctx,
377 (const GLfloat *) ((const char *) save->
378 vertex_store->buffer +
379 node->buffer_offset),
380 node->attrsz, node->prim, node->prim_count,
381 node->wrap_count, node->vertex_size);
382
383 _glapi_set_dispatch(dispatch);
384 }
385
386 /* Decide whether the storage structs are full, or can be used for
387 * the next vertex lists as well.
388 */
389 if (save->vertex_store->used >
390 VBO_SAVE_BUFFER_SIZE - 16 * (save->vertex_size + 4)) {
391
392 /* Unmap old store:
393 */
394 unmap_vertex_store(ctx, save->vertex_store);
395
396 /* Release old reference:
397 */
398 save->vertex_store->refcount--;
399 assert(save->vertex_store->refcount != 0);
400 save->vertex_store = NULL;
401
402 /* Allocate and map new store:
403 */
404 save->vertex_store = alloc_vertex_store(ctx);
405 save->buffer_ptr = map_vertex_store(ctx, save->vertex_store);
406 save->out_of_memory = save->buffer_ptr == NULL;
407 }
408
409 if (save->prim_store->used > VBO_SAVE_PRIM_SIZE - 6) {
410 save->prim_store->refcount--;
411 assert(save->prim_store->refcount != 0);
412 save->prim_store = alloc_prim_store(ctx);
413 }
414
415 /* Reset our structures for the next run of vertices:
416 */
417 _save_reset_counters(ctx);
418 }
419
420
421 /**
422 * TODO -- If no new vertices have been stored, don't bother saving it.
423 */
424 static void
425 _save_wrap_buffers(struct gl_context *ctx)
426 {
427 struct vbo_save_context *save = &vbo_context(ctx)->save;
428 GLint i = save->prim_count - 1;
429 GLenum mode;
430 GLboolean weak;
431 GLboolean no_current_update;
432
433 assert(i < (GLint) save->prim_max);
434 assert(i >= 0);
435
436 /* Close off in-progress primitive.
437 */
438 save->prim[i].count = (save->vert_count - save->prim[i].start);
439 mode = save->prim[i].mode;
440 weak = save->prim[i].weak;
441 no_current_update = save->prim[i].no_current_update;
442
443 /* store the copied vertices, and allocate a new list.
444 */
445 _save_compile_vertex_list(ctx);
446
447 /* Restart interrupted primitive
448 */
449 save->prim[0].mode = mode;
450 save->prim[0].weak = weak;
451 save->prim[0].no_current_update = no_current_update;
452 save->prim[0].begin = 0;
453 save->prim[0].end = 0;
454 save->prim[0].pad = 0;
455 save->prim[0].start = 0;
456 save->prim[0].count = 0;
457 save->prim[0].num_instances = 1;
458 save->prim_count = 1;
459 }
460
461
462 /**
463 * Called only when buffers are wrapped as the result of filling the
464 * vertex_store struct.
465 */
466 static void
467 _save_wrap_filled_vertex(struct gl_context *ctx)
468 {
469 struct vbo_save_context *save = &vbo_context(ctx)->save;
470 GLfloat *data = save->copied.buffer;
471 GLuint i;
472
473 /* Emit a glEnd to close off the last vertex list.
474 */
475 _save_wrap_buffers(ctx);
476
477 /* Copy stored stored vertices to start of new list.
478 */
479 assert(save->max_vert - save->vert_count > save->copied.nr);
480
481 for (i = 0; i < save->copied.nr; i++) {
482 memcpy(save->buffer_ptr, data, save->vertex_size * sizeof(GLfloat));
483 data += save->vertex_size;
484 save->buffer_ptr += save->vertex_size;
485 save->vert_count++;
486 }
487 }
488
489
490 static void
491 _save_copy_to_current(struct gl_context *ctx)
492 {
493 struct vbo_save_context *save = &vbo_context(ctx)->save;
494 GLuint i;
495
496 for (i = VBO_ATTRIB_POS + 1; i < VBO_ATTRIB_MAX; i++) {
497 if (save->attrsz[i]) {
498 save->currentsz[i][0] = save->attrsz[i];
499 COPY_CLEAN_4V(save->current[i], save->attrsz[i], save->attrptr[i]);
500 }
501 }
502 }
503
504
505 static void
506 _save_copy_from_current(struct gl_context *ctx)
507 {
508 struct vbo_save_context *save = &vbo_context(ctx)->save;
509 GLint i;
510
511 for (i = VBO_ATTRIB_POS + 1; i < VBO_ATTRIB_MAX; i++) {
512 switch (save->attrsz[i]) {
513 case 4:
514 save->attrptr[i][3] = save->current[i][3];
515 case 3:
516 save->attrptr[i][2] = save->current[i][2];
517 case 2:
518 save->attrptr[i][1] = save->current[i][1];
519 case 1:
520 save->attrptr[i][0] = save->current[i][0];
521 case 0:
522 break;
523 }
524 }
525 }
526
527
528 /* Flush existing data, set new attrib size, replay copied vertices.
529 */
530 static void
531 _save_upgrade_vertex(struct gl_context *ctx, GLuint attr, GLuint newsz)
532 {
533 struct vbo_save_context *save = &vbo_context(ctx)->save;
534 GLuint oldsz;
535 GLuint i;
536 GLfloat *tmp;
537
538 /* Store the current run of vertices, and emit a GL_END. Emit a
539 * BEGIN in the new buffer.
540 */
541 if (save->vert_count)
542 _save_wrap_buffers(ctx);
543 else
544 assert(save->copied.nr == 0);
545
546 /* Do a COPY_TO_CURRENT to ensure back-copying works for the case
547 * when the attribute already exists in the vertex and is having
548 * its size increased.
549 */
550 _save_copy_to_current(ctx);
551
552 /* Fix up sizes:
553 */
554 oldsz = save->attrsz[attr];
555 save->attrsz[attr] = newsz;
556
557 save->vertex_size += newsz - oldsz;
558 save->max_vert = ((VBO_SAVE_BUFFER_SIZE - save->vertex_store->used) /
559 save->vertex_size);
560 save->vert_count = 0;
561
562 /* Recalculate all the attrptr[] values:
563 */
564 for (i = 0, tmp = save->vertex; i < VBO_ATTRIB_MAX; i++) {
565 if (save->attrsz[i]) {
566 save->attrptr[i] = tmp;
567 tmp += save->attrsz[i];
568 }
569 else {
570 save->attrptr[i] = NULL; /* will not be dereferenced. */
571 }
572 }
573
574 /* Copy from current to repopulate the vertex with correct values.
575 */
576 _save_copy_from_current(ctx);
577
578 /* Replay stored vertices to translate them to new format here.
579 *
580 * If there are copied vertices and the new (upgraded) attribute
581 * has not been defined before, this list is somewhat degenerate,
582 * and will need fixup at runtime.
583 */
584 if (save->copied.nr) {
585 GLfloat *data = save->copied.buffer;
586 GLfloat *dest = save->buffer;
587 GLuint j;
588
589 /* Need to note this and fix up at runtime (or loopback):
590 */
591 if (attr != VBO_ATTRIB_POS && save->currentsz[attr][0] == 0) {
592 assert(oldsz == 0);
593 save->dangling_attr_ref = GL_TRUE;
594 }
595
596 for (i = 0; i < save->copied.nr; i++) {
597 for (j = 0; j < VBO_ATTRIB_MAX; j++) {
598 if (save->attrsz[j]) {
599 if (j == attr) {
600 if (oldsz) {
601 COPY_CLEAN_4V(dest, oldsz, data);
602 data += oldsz;
603 dest += newsz;
604 }
605 else {
606 COPY_SZ_4V(dest, newsz, save->current[attr]);
607 dest += newsz;
608 }
609 }
610 else {
611 GLint sz = save->attrsz[j];
612 COPY_SZ_4V(dest, sz, data);
613 data += sz;
614 dest += sz;
615 }
616 }
617 }
618 }
619
620 save->buffer_ptr = dest;
621 save->vert_count += save->copied.nr;
622 }
623 }
624
625
626 static void
627 save_fixup_vertex(struct gl_context *ctx, GLuint attr, GLuint sz)
628 {
629 struct vbo_save_context *save = &vbo_context(ctx)->save;
630
631 if (sz > save->attrsz[attr]) {
632 /* New size is larger. Need to flush existing vertices and get
633 * an enlarged vertex format.
634 */
635 _save_upgrade_vertex(ctx, attr, sz);
636 }
637 else if (sz < save->active_sz[attr]) {
638 static GLfloat id[4] = { 0, 0, 0, 1 };
639 GLuint i;
640
641 /* New size is equal or smaller - just need to fill in some
642 * zeros.
643 */
644 for (i = sz; i <= save->attrsz[attr]; i++)
645 save->attrptr[attr][i - 1] = id[i - 1];
646 }
647
648 save->active_sz[attr] = sz;
649 }
650
651
652 static void
653 _save_reset_vertex(struct gl_context *ctx)
654 {
655 struct vbo_save_context *save = &vbo_context(ctx)->save;
656 GLuint i;
657
658 for (i = 0; i < VBO_ATTRIB_MAX; i++) {
659 save->attrsz[i] = 0;
660 save->active_sz[i] = 0;
661 }
662
663 save->vertex_size = 0;
664 }
665
666
667
668 #define ERROR(err) _mesa_compile_error(ctx, err, __FUNCTION__);
669
670
671 /* Only one size for each attribute may be active at once. Eg. if
672 * Color3f is installed/active, then Color4f may not be, even if the
673 * vertex actually contains 4 color coordinates. This is because the
674 * 3f version won't otherwise set color[3] to 1.0 -- this is the job
675 * of the chooser function when switching between Color4f and Color3f.
676 */
677 #define ATTR(A, N, V0, V1, V2, V3) \
678 do { \
679 struct vbo_save_context *save = &vbo_context(ctx)->save; \
680 \
681 if (save->active_sz[A] != N) \
682 save_fixup_vertex(ctx, A, N); \
683 \
684 { \
685 GLfloat *dest = save->attrptr[A]; \
686 if (N>0) dest[0] = V0; \
687 if (N>1) dest[1] = V1; \
688 if (N>2) dest[2] = V2; \
689 if (N>3) dest[3] = V3; \
690 } \
691 \
692 if ((A) == 0) { \
693 GLuint i; \
694 \
695 for (i = 0; i < save->vertex_size; i++) \
696 save->buffer_ptr[i] = save->vertex[i]; \
697 \
698 save->buffer_ptr += save->vertex_size; \
699 \
700 if (++save->vert_count >= save->max_vert) \
701 _save_wrap_filled_vertex(ctx); \
702 } \
703 } while (0)
704
705 #define TAG(x) _save_##x
706
707 #include "vbo_attrib_tmp.h"
708
709
710
711 #define MAT( ATTR, N, face, params ) \
712 do { \
713 if (face != GL_BACK) \
714 MAT_ATTR( ATTR, N, params ); /* front */ \
715 if (face != GL_FRONT) \
716 MAT_ATTR( ATTR + 1, N, params ); /* back */ \
717 } while (0)
718
719
720 /**
721 * Save a glMaterial call found between glBegin/End.
722 * glMaterial calls outside Begin/End are handled in dlist.c.
723 */
724 static void GLAPIENTRY
725 _save_Materialfv(GLenum face, GLenum pname, const GLfloat *params)
726 {
727 GET_CURRENT_CONTEXT(ctx);
728
729 if (face != GL_FRONT && face != GL_BACK && face != GL_FRONT_AND_BACK) {
730 _mesa_compile_error(ctx, GL_INVALID_ENUM, "glMaterial(face)");
731 return;
732 }
733
734 __debugbreak();
735
736 switch (pname) {
737 case GL_EMISSION:
738 MAT(VBO_ATTRIB_MAT_FRONT_EMISSION, 4, face, params);
739 break;
740 case GL_AMBIENT:
741 MAT(VBO_ATTRIB_MAT_FRONT_AMBIENT, 4, face, params);
742 break;
743 case GL_DIFFUSE:
744 MAT(VBO_ATTRIB_MAT_FRONT_DIFFUSE, 4, face, params);
745 break;
746 case GL_SPECULAR:
747 MAT(VBO_ATTRIB_MAT_FRONT_SPECULAR, 4, face, params);
748 break;
749 case GL_SHININESS:
750 if (*params < 0 || *params > ctx->Const.MaxShininess) {
751 _mesa_compile_error(ctx, GL_INVALID_VALUE, "glMaterial(shininess)");
752 }
753 else {
754 MAT(VBO_ATTRIB_MAT_FRONT_SHININESS, 1, face, params);
755 }
756 break;
757 case GL_COLOR_INDEXES:
758 MAT(VBO_ATTRIB_MAT_FRONT_INDEXES, 3, face, params);
759 break;
760 case GL_AMBIENT_AND_DIFFUSE:
761 MAT(VBO_ATTRIB_MAT_FRONT_AMBIENT, 4, face, params);
762 MAT(VBO_ATTRIB_MAT_FRONT_DIFFUSE, 4, face, params);
763 break;
764 default:
765 _mesa_compile_error(ctx, GL_INVALID_ENUM, "glMaterial(pname)");
766 return;
767 }
768 }
769
770
771 /* Cope with EvalCoord/CallList called within a begin/end object:
772 * -- Flush current buffer
773 * -- Fallback to opcodes for the rest of the begin/end object.
774 */
775 static void
776 dlist_fallback(struct gl_context *ctx)
777 {
778 struct vbo_save_context *save = &vbo_context(ctx)->save;
779
780 if (save->vert_count || save->prim_count) {
781 if (save->prim_count > 0) {
782 /* Close off in-progress primitive. */
783 GLint i = save->prim_count - 1;
784 save->prim[i].count = save->vert_count - save->prim[i].start;
785 }
786
787 /* Need to replay this display list with loopback,
788 * unfortunately, otherwise this primitive won't be handled
789 * properly:
790 */
791 save->dangling_attr_ref = 1;
792
793 _save_compile_vertex_list(ctx);
794 }
795
796 _save_copy_to_current(ctx);
797 _save_reset_vertex(ctx);
798 _save_reset_counters(ctx);
799 if (save->out_of_memory) {
800 _mesa_install_save_vtxfmt(ctx, &save->vtxfmt_noop);
801 }
802 else {
803 _mesa_install_save_vtxfmt(ctx, &ctx->ListState.ListVtxfmt);
804 }
805 ctx->Driver.SaveNeedFlush = 0;
806 }
807
808
809 static void GLAPIENTRY
810 _save_EvalCoord1f(GLfloat u)
811 {
812 GET_CURRENT_CONTEXT(ctx);
813 dlist_fallback(ctx);
814 CALL_EvalCoord1f(ctx->Save, (u));
815 }
816
817 static void GLAPIENTRY
818 _save_EvalCoord1fv(const GLfloat * v)
819 {
820 GET_CURRENT_CONTEXT(ctx);
821 dlist_fallback(ctx);
822 CALL_EvalCoord1fv(ctx->Save, (v));
823 }
824
825 static void GLAPIENTRY
826 _save_EvalCoord2f(GLfloat u, GLfloat v)
827 {
828 GET_CURRENT_CONTEXT(ctx);
829 dlist_fallback(ctx);
830 CALL_EvalCoord2f(ctx->Save, (u, v));
831 }
832
833 static void GLAPIENTRY
834 _save_EvalCoord2fv(const GLfloat * v)
835 {
836 GET_CURRENT_CONTEXT(ctx);
837 dlist_fallback(ctx);
838 CALL_EvalCoord2fv(ctx->Save, (v));
839 }
840
841 static void GLAPIENTRY
842 _save_EvalPoint1(GLint i)
843 {
844 GET_CURRENT_CONTEXT(ctx);
845 dlist_fallback(ctx);
846 CALL_EvalPoint1(ctx->Save, (i));
847 }
848
849 static void GLAPIENTRY
850 _save_EvalPoint2(GLint i, GLint j)
851 {
852 GET_CURRENT_CONTEXT(ctx);
853 dlist_fallback(ctx);
854 CALL_EvalPoint2(ctx->Save, (i, j));
855 }
856
857 static void GLAPIENTRY
858 _save_CallList(GLuint l)
859 {
860 GET_CURRENT_CONTEXT(ctx);
861 dlist_fallback(ctx);
862 CALL_CallList(ctx->Save, (l));
863 }
864
865 static void GLAPIENTRY
866 _save_CallLists(GLsizei n, GLenum type, const GLvoid * v)
867 {
868 GET_CURRENT_CONTEXT(ctx);
869 dlist_fallback(ctx);
870 CALL_CallLists(ctx->Save, (n, type, v));
871 }
872
873
874
875 /* This begin is hooked into ... Updating of
876 * ctx->Driver.CurrentSavePrimitive is already taken care of.
877 */
878 GLboolean
879 vbo_save_NotifyBegin(struct gl_context *ctx, GLenum mode)
880 {
881 struct vbo_save_context *save = &vbo_context(ctx)->save;
882
883 GLuint i = save->prim_count++;
884
885 assert(i < save->prim_max);
886 save->prim[i].mode = mode & VBO_SAVE_PRIM_MODE_MASK;
887 save->prim[i].begin = 1;
888 save->prim[i].end = 0;
889 save->prim[i].weak = (mode & VBO_SAVE_PRIM_WEAK) ? 1 : 0;
890 save->prim[i].no_current_update =
891 (mode & VBO_SAVE_PRIM_NO_CURRENT_UPDATE) ? 1 : 0;
892 save->prim[i].pad = 0;
893 save->prim[i].start = save->vert_count;
894 save->prim[i].count = 0;
895 save->prim[i].num_instances = 1;
896
897 if (save->out_of_memory) {
898 _mesa_install_save_vtxfmt(ctx, &save->vtxfmt_noop);
899 }
900 else {
901 _mesa_install_save_vtxfmt(ctx, &save->vtxfmt);
902 }
903 ctx->Driver.SaveNeedFlush = 1;
904 return GL_TRUE;
905 }
906
907
908 static void GLAPIENTRY
909 _save_End(void)
910 {
911 GET_CURRENT_CONTEXT(ctx);
912 struct vbo_save_context *save = &vbo_context(ctx)->save;
913 GLint i = save->prim_count - 1;
914
915 ctx->Driver.CurrentSavePrimitive = PRIM_OUTSIDE_BEGIN_END;
916 save->prim[i].end = 1;
917 save->prim[i].count = (save->vert_count - save->prim[i].start);
918
919 if (i == (GLint) save->prim_max - 1) {
920 _save_compile_vertex_list(ctx);
921 assert(save->copied.nr == 0);
922 }
923
924 /* Swap out this vertex format while outside begin/end. Any color,
925 * etc. received between here and the next begin will be compiled
926 * as opcodes.
927 */
928 if (save->out_of_memory) {
929 _mesa_install_save_vtxfmt(ctx, &save->vtxfmt_noop);
930 }
931 else {
932 _mesa_install_save_vtxfmt(ctx, &ctx->ListState.ListVtxfmt);
933 }
934 }
935
936
937 /* These are all errors as this vtxfmt is only installed inside
938 * begin/end pairs.
939 */
940 static void GLAPIENTRY
941 _save_DrawElements(GLenum mode, GLsizei count, GLenum type,
942 const GLvoid * indices)
943 {
944 GET_CURRENT_CONTEXT(ctx);
945 (void) mode;
946 (void) count;
947 (void) type;
948 (void) indices;
949 _mesa_compile_error(ctx, GL_INVALID_OPERATION, "glDrawElements");
950 }
951
952
953 static void GLAPIENTRY
954 _save_DrawArrays(GLenum mode, GLint start, GLsizei count)
955 {
956 GET_CURRENT_CONTEXT(ctx);
957 (void) mode;
958 (void) start;
959 (void) count;
960 _mesa_compile_error(ctx, GL_INVALID_OPERATION, "glDrawArrays");
961 }
962
963
964 static void GLAPIENTRY
965 _save_Rectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
966 {
967 GET_CURRENT_CONTEXT(ctx);
968 (void) x1;
969 (void) y1;
970 (void) x2;
971 (void) y2;
972 _mesa_compile_error(ctx, GL_INVALID_OPERATION, "glRectf");
973 }
974
975
976 static void GLAPIENTRY
977 _save_EvalMesh1(GLenum mode, GLint i1, GLint i2)
978 {
979 GET_CURRENT_CONTEXT(ctx);
980 (void) mode;
981 (void) i1;
982 (void) i2;
983 _mesa_compile_error(ctx, GL_INVALID_OPERATION, "glEvalMesh1");
984 }
985
986
987 static void GLAPIENTRY
988 _save_EvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2)
989 {
990 GET_CURRENT_CONTEXT(ctx);
991 (void) mode;
992 (void) i1;
993 (void) i2;
994 (void) j1;
995 (void) j2;
996 _mesa_compile_error(ctx, GL_INVALID_OPERATION, "glEvalMesh2");
997 }
998
999
1000 static void GLAPIENTRY
1001 _save_Begin(GLenum mode)
1002 {
1003 GET_CURRENT_CONTEXT(ctx);
1004 (void) mode;
1005 _mesa_compile_error(ctx, GL_INVALID_OPERATION, "Recursive glBegin");
1006 }
1007
1008
1009 /* Unlike the functions above, these are to be hooked into the vtxfmt
1010 * maintained in ctx->ListState, active when the list is known or
1011 * suspected to be outside any begin/end primitive.
1012 * Note: OBE = Outside Begin/End
1013 */
1014 static void GLAPIENTRY
1015 _save_OBE_Rectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
1016 {
1017 GET_CURRENT_CONTEXT(ctx);
1018 vbo_save_NotifyBegin(ctx, GL_QUADS | VBO_SAVE_PRIM_WEAK);
1019 CALL_Vertex2f(GET_DISPATCH(), (x1, y1));
1020 CALL_Vertex2f(GET_DISPATCH(), (x2, y1));
1021 CALL_Vertex2f(GET_DISPATCH(), (x2, y2));
1022 CALL_Vertex2f(GET_DISPATCH(), (x1, y2));
1023 CALL_End(GET_DISPATCH(), ());
1024 }
1025
1026
1027 static void GLAPIENTRY
1028 _save_OBE_DrawArrays(GLenum mode, GLint start, GLsizei count)
1029 {
1030 GET_CURRENT_CONTEXT(ctx);
1031 struct vbo_save_context *save = &vbo_context(ctx)->save;
1032 GLint i;
1033
1034 if (!_mesa_validate_DrawArrays(ctx, mode, start, count))
1035 return;
1036
1037 if (save->out_of_memory)
1038 return;
1039
1040 _ae_map_vbos(ctx);
1041
1042 vbo_save_NotifyBegin(ctx, (mode | VBO_SAVE_PRIM_WEAK
1043 | VBO_SAVE_PRIM_NO_CURRENT_UPDATE));
1044
1045 for (i = 0; i < count; i++)
1046 CALL_ArrayElement(GET_DISPATCH(), (start + i));
1047 CALL_End(GET_DISPATCH(), ());
1048
1049 _ae_unmap_vbos(ctx);
1050 }
1051
1052
1053 /* Could do better by copying the arrays and element list intact and
1054 * then emitting an indexed prim at runtime.
1055 */
1056 static void GLAPIENTRY
1057 _save_OBE_DrawElements(GLenum mode, GLsizei count, GLenum type,
1058 const GLvoid * indices)
1059 {
1060 GET_CURRENT_CONTEXT(ctx);
1061 struct vbo_save_context *save = &vbo_context(ctx)->save;
1062 GLint i;
1063
1064 if (!_mesa_validate_DrawElements(ctx, mode, count, type, indices))
1065 return;
1066
1067 if (save->out_of_memory)
1068 return;
1069
1070 _ae_map_vbos(ctx);
1071
1072 if (_mesa_is_bufferobj(ctx->Array.ElementArrayBufferObj))
1073 indices = ADD_POINTERS(ctx->Array.ElementArrayBufferObj->Pointer, indices);
1074
1075 vbo_save_NotifyBegin(ctx, (mode | VBO_SAVE_PRIM_WEAK |
1076 VBO_SAVE_PRIM_NO_CURRENT_UPDATE));
1077
1078 switch (type) {
1079 case GL_UNSIGNED_BYTE:
1080 for (i = 0; i < count; i++)
1081 CALL_ArrayElement(GET_DISPATCH(), (((GLubyte *) indices)[i]));
1082 break;
1083 case GL_UNSIGNED_SHORT:
1084 for (i = 0; i < count; i++)
1085 CALL_ArrayElement(GET_DISPATCH(), (((GLushort *) indices)[i]));
1086 break;
1087 case GL_UNSIGNED_INT:
1088 for (i = 0; i < count; i++)
1089 CALL_ArrayElement(GET_DISPATCH(), (((GLuint *) indices)[i]));
1090 break;
1091 default:
1092 _mesa_error(ctx, GL_INVALID_ENUM, "glDrawElements(type)");
1093 break;
1094 }
1095
1096 CALL_End(GET_DISPATCH(), ());
1097
1098 _ae_unmap_vbos(ctx);
1099 }
1100
1101
1102 static void
1103 _save_vtxfmt_init(struct gl_context *ctx)
1104 {
1105 struct vbo_save_context *save = &vbo_context(ctx)->save;
1106 GLvertexformat *vfmt = &save->vtxfmt;
1107
1108 _MESA_INIT_ARRAYELT_VTXFMT(vfmt, _ae_);
1109
1110 vfmt->Begin = _save_Begin;
1111 vfmt->Color3f = _save_Color3f;
1112 vfmt->Color3fv = _save_Color3fv;
1113 vfmt->Color4f = _save_Color4f;
1114 vfmt->Color4fv = _save_Color4fv;
1115 vfmt->EdgeFlag = _save_EdgeFlag;
1116 vfmt->End = _save_End;
1117 vfmt->FogCoordfEXT = _save_FogCoordfEXT;
1118 vfmt->FogCoordfvEXT = _save_FogCoordfvEXT;
1119 vfmt->Indexf = _save_Indexf;
1120 vfmt->Indexfv = _save_Indexfv;
1121 vfmt->Materialfv = _save_Materialfv;
1122 vfmt->Normal3f = _save_Normal3f;
1123 vfmt->Normal3fv = _save_Normal3fv;
1124 vfmt->TexCoord1f = _save_TexCoord1f;
1125 vfmt->TexCoord1fv = _save_TexCoord1fv;
1126 vfmt->TexCoord2f = _save_TexCoord2f;
1127 vfmt->TexCoord2fv = _save_TexCoord2fv;
1128 vfmt->TexCoord3f = _save_TexCoord3f;
1129 vfmt->TexCoord3fv = _save_TexCoord3fv;
1130 vfmt->TexCoord4f = _save_TexCoord4f;
1131 vfmt->TexCoord4fv = _save_TexCoord4fv;
1132 vfmt->Vertex2f = _save_Vertex2f;
1133 vfmt->Vertex2fv = _save_Vertex2fv;
1134 vfmt->Vertex3f = _save_Vertex3f;
1135 vfmt->Vertex3fv = _save_Vertex3fv;
1136 vfmt->Vertex4f = _save_Vertex4f;
1137 vfmt->Vertex4fv = _save_Vertex4fv;
1138
1139 vfmt->VertexAttrib1fNV = _save_VertexAttrib1fNV;
1140 vfmt->VertexAttrib1fvNV = _save_VertexAttrib1fvNV;
1141 vfmt->VertexAttrib2fNV = _save_VertexAttrib2fNV;
1142 vfmt->VertexAttrib2fvNV = _save_VertexAttrib2fvNV;
1143 vfmt->VertexAttrib3fNV = _save_VertexAttrib3fNV;
1144 vfmt->VertexAttrib3fvNV = _save_VertexAttrib3fvNV;
1145 vfmt->VertexAttrib4fNV = _save_VertexAttrib4fNV;
1146 vfmt->VertexAttrib4fvNV = _save_VertexAttrib4fvNV;
1147
1148 /* This will all require us to fallback to saving the list as opcodes:
1149 */
1150 _MESA_INIT_DLIST_VTXFMT(vfmt, _save_); /* inside begin/end */
1151
1152 _MESA_INIT_EVAL_VTXFMT(vfmt, _save_);
1153
1154 /* These calls all generate GL_INVALID_OPERATION since this vtxfmt is
1155 * only used when we're inside a glBegin/End pair.
1156 */
1157 vfmt->Begin = _save_Begin;
1158 vfmt->Rectf = _save_Rectf;
1159 vfmt->DrawArrays = _save_DrawArrays;
1160 vfmt->DrawElements = _save_DrawElements;
1161 }
1162
1163
1164 void
1165 vbo_save_SaveFlushVertices(struct gl_context *ctx)
1166 {
1167 struct vbo_save_context *save = &vbo_context(ctx)->save;
1168
1169 /* Noop when we are actually active:
1170 */
1171 if (ctx->Driver.CurrentSavePrimitive == PRIM_INSIDE_UNKNOWN_PRIM ||
1172 ctx->Driver.CurrentSavePrimitive <= GL_POLYGON)
1173 return;
1174
1175 if (save->vert_count || save->prim_count)
1176 _save_compile_vertex_list(ctx);
1177
1178 _save_copy_to_current(ctx);
1179 _save_reset_vertex(ctx);
1180 _save_reset_counters(ctx);
1181 ctx->Driver.SaveNeedFlush = 0;
1182 }
1183
1184
1185 void
1186 vbo_save_NewList(struct gl_context *ctx, GLuint list, GLenum mode)
1187 {
1188 struct vbo_save_context *save = &vbo_context(ctx)->save;
1189
1190 (void) list;
1191 (void) mode;
1192
1193 if (!save->prim_store)
1194 save->prim_store = alloc_prim_store(ctx);
1195
1196 if (!save->vertex_store)
1197 save->vertex_store = alloc_vertex_store(ctx);
1198
1199 save->buffer_ptr = map_vertex_store(ctx, save->vertex_store);
1200
1201 _save_reset_vertex(ctx);
1202 _save_reset_counters(ctx);
1203 ctx->Driver.SaveNeedFlush = 0;
1204 }
1205
1206
1207 void
1208 vbo_save_EndList(struct gl_context *ctx)
1209 {
1210 struct vbo_save_context *save = &vbo_context(ctx)->save;
1211
1212 /* EndList called inside a (saved) Begin/End pair?
1213 */
1214 if (ctx->Driver.CurrentSavePrimitive != PRIM_OUTSIDE_BEGIN_END) {
1215
1216 if (save->prim_count > 0) {
1217 GLint i = save->prim_count - 1;
1218 ctx->Driver.CurrentSavePrimitive = PRIM_OUTSIDE_BEGIN_END;
1219 save->prim[i].end = 0;
1220 save->prim[i].count = (save->vert_count - save->prim[i].start);
1221 }
1222
1223 /* Make sure this vertex list gets replayed by the "loopback"
1224 * mechanism:
1225 */
1226 save->dangling_attr_ref = 1;
1227 vbo_save_SaveFlushVertices(ctx);
1228
1229 /* Swap out this vertex format while outside begin/end. Any color,
1230 * etc. received between here and the next begin will be compiled
1231 * as opcodes.
1232 */
1233 _mesa_install_save_vtxfmt(ctx, &ctx->ListState.ListVtxfmt);
1234 }
1235
1236 unmap_vertex_store(ctx, save->vertex_store);
1237
1238 assert(save->vertex_size == 0);
1239 }
1240
1241
1242 void
1243 vbo_save_BeginCallList(struct gl_context *ctx, struct gl_display_list *dlist)
1244 {
1245 struct vbo_save_context *save = &vbo_context(ctx)->save;
1246 save->replay_flags |= dlist->Flags;
1247 }
1248
1249
1250 void
1251 vbo_save_EndCallList(struct gl_context *ctx)
1252 {
1253 struct vbo_save_context *save = &vbo_context(ctx)->save;
1254
1255 if (ctx->ListState.CallDepth == 1) {
1256 /* This is correct: want to keep only the VBO_SAVE_FALLBACK
1257 * flag, if it is set:
1258 */
1259 save->replay_flags &= VBO_SAVE_FALLBACK;
1260 }
1261 }
1262
1263
1264 static void
1265 vbo_destroy_vertex_list(struct gl_context *ctx, void *data)
1266 {
1267 struct vbo_save_vertex_list *node = (struct vbo_save_vertex_list *) data;
1268 (void) ctx;
1269
1270 if (--node->vertex_store->refcount == 0)
1271 free_vertex_store(ctx, node->vertex_store);
1272
1273 if (--node->prim_store->refcount == 0)
1274 FREE(node->prim_store);
1275
1276 if (node->current_data) {
1277 FREE(node->current_data);
1278 node->current_data = NULL;
1279 }
1280 }
1281
1282
1283 static void
1284 vbo_print_vertex_list(struct gl_context *ctx, void *data)
1285 {
1286 struct vbo_save_vertex_list *node = (struct vbo_save_vertex_list *) data;
1287 GLuint i;
1288 (void) ctx;
1289
1290 printf("VBO-VERTEX-LIST, %u vertices %d primitives, %d vertsize\n",
1291 node->count, node->prim_count, node->vertex_size);
1292
1293 for (i = 0; i < node->prim_count; i++) {
1294 struct _mesa_prim *prim = &node->prim[i];
1295 _mesa_debug(NULL, " prim %d: %s%s %d..%d %s %s\n",
1296 i,
1297 _mesa_lookup_prim_by_nr(prim->mode),
1298 prim->weak ? " (weak)" : "",
1299 prim->start,
1300 prim->start + prim->count,
1301 (prim->begin) ? "BEGIN" : "(wrap)",
1302 (prim->end) ? "END" : "(wrap)");
1303 }
1304 }
1305
1306
1307 static void
1308 _save_current_init(struct gl_context *ctx)
1309 {
1310 struct vbo_save_context *save = &vbo_context(ctx)->save;
1311 GLint i;
1312
1313 for (i = VBO_ATTRIB_POS; i <= VBO_ATTRIB_POINT_SIZE; i++) {
1314 const GLuint j = i - VBO_ATTRIB_POS;
1315 ASSERT(j < VERT_ATTRIB_MAX);
1316 save->currentsz[i] = &ctx->ListState.ActiveAttribSize[j];
1317 save->current[i] = ctx->ListState.CurrentAttrib[j];
1318 }
1319
1320 for (i = VBO_ATTRIB_FIRST_MATERIAL; i <= VBO_ATTRIB_LAST_MATERIAL; i++) {
1321 const GLuint j = i - VBO_ATTRIB_FIRST_MATERIAL;
1322 ASSERT(j < MAT_ATTRIB_MAX);
1323 save->currentsz[i] = &ctx->ListState.ActiveMaterialSize[j];
1324 save->current[i] = ctx->ListState.CurrentMaterial[j];
1325 }
1326 }
1327
1328
1329 /**
1330 * Initialize the display list compiler
1331 */
1332 void
1333 vbo_save_api_init(struct vbo_save_context *save)
1334 {
1335 struct gl_context *ctx = save->ctx;
1336 GLuint i;
1337
1338 save->opcode_vertex_list =
1339 _mesa_dlist_alloc_opcode(ctx,
1340 sizeof(struct vbo_save_vertex_list),
1341 vbo_save_playback_vertex_list,
1342 vbo_destroy_vertex_list,
1343 vbo_print_vertex_list);
1344
1345 ctx->Driver.NotifySaveBegin = vbo_save_NotifyBegin;
1346
1347 _save_vtxfmt_init(ctx);
1348 _save_current_init(ctx);
1349 _mesa_noop_vtxfmt_init(&save->vtxfmt_noop);
1350
1351 /* These will actually get set again when binding/drawing */
1352 for (i = 0; i < VBO_ATTRIB_MAX; i++)
1353 save->inputs[i] = &save->arrays[i];
1354
1355 /* Hook our array functions into the outside-begin-end vtxfmt in
1356 * ctx->ListState.
1357 */
1358 ctx->ListState.ListVtxfmt.Rectf = _save_OBE_Rectf;
1359 ctx->ListState.ListVtxfmt.DrawArrays = _save_OBE_DrawArrays;
1360 ctx->ListState.ListVtxfmt.DrawElements = _save_OBE_DrawElements;
1361 _mesa_install_save_vtxfmt(ctx, &ctx->ListState.ListVtxfmt);
1362 }
1363
1364
1365 #endif /* FEATURE_dlist */