• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Mesa 3-D graphics library
3  *
4  * Copyright (C) 1999-2008  Brian Paul   All Rights Reserved.
5  * Copyright (C) 2009  VMware, Inc.  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  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11  * and/or sell copies of the Software, and to permit persons to whom the
12  * Software is furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23  * OTHER DEALINGS IN THE SOFTWARE.
24  */
25 
26 /**
27  * \file feedback.c
28  * Selection and feedback modes functions.
29  */
30 
31 
32 #include "glheader.h"
33 #include "c99_alloca.h"
34 #include "context.h"
35 #include "enums.h"
36 #include "feedback.h"
37 #include "macros.h"
38 #include "mtypes.h"
39 #include "api_exec_decl.h"
40 #include "bufferobj.h"
41 
42 #include "state_tracker/st_cb_feedback.h"
43 
44 #define FB_3D		0x01
45 #define FB_4D		0x02
46 #define FB_COLOR	0x04
47 #define FB_TEXTURE	0X08
48 
49 
50 
51 void GLAPIENTRY
_mesa_FeedbackBuffer(GLsizei size,GLenum type,GLfloat * buffer)52 _mesa_FeedbackBuffer( GLsizei size, GLenum type, GLfloat *buffer )
53 {
54    GET_CURRENT_CONTEXT(ctx);
55 
56    if (ctx->RenderMode==GL_FEEDBACK) {
57       _mesa_error( ctx, GL_INVALID_OPERATION, "glFeedbackBuffer" );
58       return;
59    }
60    if (size<0) {
61       _mesa_error( ctx, GL_INVALID_VALUE, "glFeedbackBuffer(size<0)" );
62       return;
63    }
64    if (!buffer && size > 0) {
65       _mesa_error( ctx, GL_INVALID_VALUE, "glFeedbackBuffer(buffer==NULL)" );
66       ctx->Feedback.BufferSize = 0;
67       return;
68    }
69 
70    switch (type) {
71       case GL_2D:
72 	 ctx->Feedback._Mask = 0;
73 	 break;
74       case GL_3D:
75 	 ctx->Feedback._Mask = FB_3D;
76 	 break;
77       case GL_3D_COLOR:
78 	 ctx->Feedback._Mask = (FB_3D | FB_COLOR);
79 	 break;
80       case GL_3D_COLOR_TEXTURE:
81 	 ctx->Feedback._Mask = (FB_3D | FB_COLOR | FB_TEXTURE);
82 	 break;
83       case GL_4D_COLOR_TEXTURE:
84 	 ctx->Feedback._Mask = (FB_3D | FB_4D | FB_COLOR | FB_TEXTURE);
85 	 break;
86       default:
87          _mesa_error( ctx, GL_INVALID_ENUM, "glFeedbackBuffer" );
88 	 return;
89    }
90 
91    FLUSH_VERTICES(ctx, _NEW_RENDERMODE, 0); /* Always flush */
92    ctx->Feedback.Type = type;
93    ctx->Feedback.BufferSize = size;
94    ctx->Feedback.Buffer = buffer;
95    ctx->Feedback.Count = 0;	              /* Because of this. */
96 }
97 
98 
99 void GLAPIENTRY
_mesa_PassThrough(GLfloat token)100 _mesa_PassThrough( GLfloat token )
101 {
102    GET_CURRENT_CONTEXT(ctx);
103 
104    if (ctx->RenderMode==GL_FEEDBACK) {
105       FLUSH_VERTICES(ctx, 0, 0);
106       _mesa_feedback_token( ctx, (GLfloat) (GLint) GL_PASS_THROUGH_TOKEN );
107       _mesa_feedback_token( ctx, token );
108    }
109 }
110 
111 
112 /**
113  * Put a vertex into the feedback buffer.
114  */
115 void
_mesa_feedback_vertex(struct gl_context * ctx,const GLfloat win[4],const GLfloat color[4],const GLfloat texcoord[4])116 _mesa_feedback_vertex(struct gl_context *ctx,
117                       const GLfloat win[4],
118                       const GLfloat color[4],
119                       const GLfloat texcoord[4])
120 {
121    _mesa_feedback_token( ctx, win[0] );
122    _mesa_feedback_token( ctx, win[1] );
123    if (ctx->Feedback._Mask & FB_3D) {
124       _mesa_feedback_token( ctx, win[2] );
125    }
126    if (ctx->Feedback._Mask & FB_4D) {
127       _mesa_feedback_token( ctx, win[3] );
128    }
129    if (ctx->Feedback._Mask & FB_COLOR) {
130       _mesa_feedback_token( ctx, color[0] );
131       _mesa_feedback_token( ctx, color[1] );
132       _mesa_feedback_token( ctx, color[2] );
133       _mesa_feedback_token( ctx, color[3] );
134    }
135    if (ctx->Feedback._Mask & FB_TEXTURE) {
136       _mesa_feedback_token( ctx, texcoord[0] );
137       _mesa_feedback_token( ctx, texcoord[1] );
138       _mesa_feedback_token( ctx, texcoord[2] );
139       _mesa_feedback_token( ctx, texcoord[3] );
140    }
141 }
142 
143 
144 /**********************************************************************/
145 /** \name Selection */
146 /*@{*/
147 
148 /**
149  * Establish a buffer for selection mode values.
150  *
151  * \param size buffer size.
152  * \param buffer buffer.
153  *
154  * \sa glSelectBuffer().
155  *
156  * \note this function can't be put in a display list.
157  *
158  * Verifies we're not in selection mode, flushes the vertices and initialize
159  * the fields in __struct gl_contextRec::Select with the given buffer.
160  */
161 void GLAPIENTRY
_mesa_SelectBuffer(GLsizei size,GLuint * buffer)162 _mesa_SelectBuffer( GLsizei size, GLuint *buffer )
163 {
164    GET_CURRENT_CONTEXT(ctx);
165 
166    if (size < 0) {
167       _mesa_error(ctx, GL_INVALID_VALUE, "glSelectBuffer(size)");
168       return;
169    }
170 
171    if (ctx->RenderMode==GL_SELECT) {
172       _mesa_error( ctx, GL_INVALID_OPERATION, "glSelectBuffer" );
173       return;			/* KW: added return */
174    }
175 
176    FLUSH_VERTICES(ctx, _NEW_RENDERMODE, 0);
177    ctx->Select.Buffer = buffer;
178    ctx->Select.BufferSize = size;
179    ctx->Select.BufferCount = 0;
180    ctx->Select.HitFlag = GL_FALSE;
181    ctx->Select.HitMinZ = 1.0;
182    ctx->Select.HitMaxZ = 0.0;
183 }
184 
185 
186 /**
187  * Write a value of a record into the selection buffer.
188  *
189  * \param ctx GL context.
190  * \param value value.
191  *
192  * Verifies there is free space in the buffer to write the value and
193  * increments the pointer.
194  */
195 static inline void
write_record(struct gl_context * ctx,GLuint value)196 write_record(struct gl_context *ctx, GLuint value)
197 {
198    if (ctx->Select.BufferCount < ctx->Select.BufferSize) {
199       ctx->Select.Buffer[ctx->Select.BufferCount] = value;
200    }
201    ctx->Select.BufferCount++;
202 }
203 
204 
205 /**
206  * Update the hit flag and the maximum and minimum depth values.
207  *
208  * \param ctx GL context.
209  * \param z depth.
210  *
211  * Sets gl_selection::HitFlag and updates gl_selection::HitMinZ and
212  * gl_selection::HitMaxZ.
213  */
214 void
_mesa_update_hitflag(struct gl_context * ctx,GLfloat z)215 _mesa_update_hitflag(struct gl_context *ctx, GLfloat z)
216 {
217    ctx->Select.HitFlag = GL_TRUE;
218    if (z < ctx->Select.HitMinZ) {
219       ctx->Select.HitMinZ = z;
220    }
221    if (z > ctx->Select.HitMaxZ) {
222       ctx->Select.HitMaxZ = z;
223    }
224 }
225 
226 static void
alloc_select_resource(struct gl_context * ctx)227 alloc_select_resource(struct gl_context *ctx)
228 {
229    struct gl_selection *s = &ctx->Select;
230 
231    if (!ctx->Const.HardwareAcceleratedSelect)
232       return;
233 
234    if (!ctx->HWSelectModeBeginEnd) {
235       ctx->HWSelectModeBeginEnd = _mesa_alloc_dispatch_table(false);
236       if (!ctx->HWSelectModeBeginEnd) {
237          _mesa_error(ctx, GL_OUT_OF_MEMORY, "Cannot allocate HWSelectModeBeginEnd");
238          return;
239       }
240       vbo_install_hw_select_begin_end(ctx);
241    }
242 
243    if (!s->SaveBuffer) {
244       s->SaveBuffer = malloc(NAME_STACK_BUFFER_SIZE);
245       if (!s->SaveBuffer) {
246          _mesa_error(ctx, GL_OUT_OF_MEMORY, "Cannot allocate name stack save buffer");
247          return;
248       }
249    }
250 
251    if (!s->Result) {
252       s->Result = _mesa_bufferobj_alloc(ctx, -1);
253       if (!s->Result) {
254          _mesa_error(ctx, GL_OUT_OF_MEMORY, "Cannot allocate select result buffer");
255          return;
256       }
257 
258       GLuint init_result[MAX_NAME_STACK_RESULT_NUM * 3];
259       for (int i = 0; i < MAX_NAME_STACK_RESULT_NUM; i++) {
260          init_result[i * 3] = 0;              /* hit */
261          init_result[i * 3 + 1] = 0xffffffff; /* minz */
262          init_result[i * 3 + 2] = 0;          /* maxz */
263       }
264 
265       bool success = _mesa_bufferobj_data(ctx,
266                                           GL_SHADER_STORAGE_BUFFER,
267                                           sizeof(init_result),
268                                           init_result,
269                                           GL_STATIC_DRAW, 0,
270                                           s->Result);
271       if (!success) {
272          _mesa_reference_buffer_object(ctx, &s->Result, NULL);
273          _mesa_error(ctx, GL_OUT_OF_MEMORY, "Cannot init result buffer");
274          return;
275       }
276    }
277 }
278 
279 static bool
save_used_name_stack(struct gl_context * ctx)280 save_used_name_stack(struct gl_context *ctx)
281 {
282    struct gl_selection *s = &ctx->Select;
283 
284    if (!ctx->Const.HardwareAcceleratedSelect)
285       return false;
286 
287    /* We have two kinds of name stack user:
288     *   1. glRasterPos (CPU based) will set HitFlag
289     *   2. draw call for GPU will set ResultUsed
290     */
291    if (!s->ResultUsed && !s->HitFlag)
292       return false;
293 
294    void *save = (char *)s->SaveBuffer + s->SaveBufferTail;
295 
296    /* save meta data */
297    uint8_t *metadata = save;
298    metadata[0] = s->HitFlag;
299    metadata[1] = s->ResultUsed;
300    metadata[2] = s->NameStackDepth;
301    metadata[3] = 0;
302 
303    /* save hit data */
304    int index = 1;
305    if (s->HitFlag) {
306       float *hit = save;
307       hit[index++] = s->HitMinZ;
308       hit[index++] = s->HitMaxZ;
309    }
310 
311    /* save name stack */
312    memcpy((uint32_t *)save + index, s->NameStack, s->NameStackDepth * sizeof(GLuint));
313    index += s->NameStackDepth;
314 
315    s->SaveBufferTail += index * sizeof(GLuint);
316    s->SavedStackNum++;
317 
318    /* if current slot has been used, store result to next slot in result buffer */
319    if (s->ResultUsed)
320       s->ResultOffset += 3 * sizeof(GLuint);
321 
322    /* reset fields */
323    s->HitFlag = GL_FALSE;
324    s->HitMinZ = 1.0;
325    s->HitMaxZ = 0;
326 
327    s->ResultUsed = GL_FALSE;
328 
329    /* return true if we have no enough space for the next name stack data */
330    return s->ResultOffset >= MAX_NAME_STACK_RESULT_NUM * 3 * sizeof(GLuint) ||
331       s->SaveBufferTail >= NAME_STACK_BUFFER_SIZE - (MAX_NAME_STACK_DEPTH + 3) * sizeof(GLuint);
332 }
333 
334 static void
update_hit_record(struct gl_context * ctx)335 update_hit_record(struct gl_context *ctx)
336 {
337    struct gl_selection *s = &ctx->Select;
338 
339    if (ctx->Const.HardwareAcceleratedSelect) {
340       if (!s->SavedStackNum)
341          return;
342 
343       unsigned size = s->ResultOffset;
344       GLuint *result = size ? alloca(size) : NULL;
345       _mesa_bufferobj_get_subdata(ctx, 0, size, result, s->Result);
346 
347       unsigned index = 0;
348       unsigned *save = s->SaveBuffer;
349       for (int i = 0; i < s->SavedStackNum; i++) {
350          uint8_t *metadata = (uint8_t *)(save++);
351 
352          unsigned zmin, zmax;
353          bool cpu_hit = !!metadata[0];
354          if (cpu_hit) {
355             /* map [0, 1] to [0, UINT_MAX]*/
356             zmin = (unsigned) ((float)(~0u) * *(float *)(save++));
357             zmax = (unsigned) ((float)(~0u) * *(float *)(save++));
358          } else {
359             zmin = ~0u;
360             zmax = 0;
361          }
362 
363          bool gpu_hit = false;
364          if (metadata[1]) {
365             gpu_hit = !!result[index];
366 
367             if (gpu_hit) {
368                zmin = MIN2(zmin, result[index + 1]);
369                zmax = MAX2(zmax, result[index + 2]);
370 
371                /* reset data */
372                result[index]     = 0;          /* hit */
373                result[index + 1] = 0xffffffff; /* minz */
374                result[index + 2] = 0;          /* maxz */
375             }
376             index += 3;
377          }
378 
379          int depth = metadata[2];
380          if (cpu_hit || gpu_hit) {
381             /* hit */
382             write_record(ctx, depth);
383             write_record(ctx, zmin);
384             write_record(ctx, zmax);
385 
386             for (int j = 0; j < depth; j++)
387                write_record(ctx, save[j]);
388             s->Hits++;
389          }
390          save += depth;
391       }
392 
393       /* reset result buffer */
394       _mesa_bufferobj_subdata(ctx, 0, size, result, s->Result);
395 
396       s->SaveBufferTail = 0;
397       s->SavedStackNum = 0;
398       s->ResultOffset = 0;
399    } else {
400       if (!s->HitFlag)
401          return;
402 
403       /* HitMinZ and HitMaxZ are in [0,1].  Multiply these values by */
404       /* 2^32-1 and round to nearest unsigned integer. */
405       GLuint zscale = (~0u);
406       GLuint zmin = (GLuint) ((GLfloat) zscale * s->HitMinZ);
407       GLuint zmax = (GLuint) ((GLfloat) zscale * s->HitMaxZ);
408 
409       write_record(ctx, s->NameStackDepth);
410       write_record(ctx, zmin);
411       write_record(ctx, zmax);
412       for (int i = 0; i < s->NameStackDepth; i++)
413          write_record(ctx, s->NameStack[i]);
414 
415       s->HitFlag = GL_FALSE;
416       s->HitMinZ = 1.0;
417       s->HitMaxZ = -1.0;
418 
419       s->Hits++;
420    }
421 }
422 
423 static void
reset_name_stack_to_empty(struct gl_context * ctx)424 reset_name_stack_to_empty(struct gl_context *ctx)
425 {
426    struct gl_selection *s = &ctx->Select;
427 
428    s->NameStackDepth = 0;
429    s->HitFlag = GL_FALSE;
430    s->HitMinZ = 1.0;
431    s->HitMaxZ = 0.0;
432 
433    if (ctx->Const.HardwareAcceleratedSelect) {
434       s->SaveBufferTail = 0;
435       s->SavedStackNum = 0;
436       s->ResultUsed = GL_FALSE;
437       s->ResultOffset = 0;
438    }
439 }
440 
441 /**
442  * Initialize the name stack.
443  */
444 void GLAPIENTRY
_mesa_InitNames(void)445 _mesa_InitNames( void )
446 {
447    GET_CURRENT_CONTEXT(ctx);
448 
449    /* Calls to glInitNames while the render mode is not GL_SELECT are ignored. */
450    if (ctx->RenderMode != GL_SELECT)
451       return;
452 
453    FLUSH_VERTICES(ctx, 0, 0);
454 
455    save_used_name_stack(ctx);
456    update_hit_record(ctx);
457 
458    reset_name_stack_to_empty(ctx);
459 
460    ctx->NewState |= _NEW_RENDERMODE;
461 }
462 
463 
464 /**
465  * Load the top-most name of the name stack.
466  *
467  * \param name name.
468  */
469 void GLAPIENTRY
_mesa_LoadName(GLuint name)470 _mesa_LoadName( GLuint name )
471 {
472    GET_CURRENT_CONTEXT(ctx);
473 
474    if (ctx->RenderMode != GL_SELECT) {
475       return;
476    }
477    if (ctx->Select.NameStackDepth == 0) {
478       _mesa_error( ctx, GL_INVALID_OPERATION, "glLoadName" );
479       return;
480    }
481 
482    if (!ctx->Const.HardwareAcceleratedSelect || save_used_name_stack(ctx)) {
483       FLUSH_VERTICES(ctx, 0, 0);
484       update_hit_record(ctx);
485    }
486 
487    ctx->Select.NameStack[ctx->Select.NameStackDepth-1] = name;
488    ctx->NewState |= _NEW_RENDERMODE;
489 }
490 
491 
492 /**
493  * Push a name into the name stack.
494  *
495  * \param name name.
496  */
497 void GLAPIENTRY
_mesa_PushName(GLuint name)498 _mesa_PushName( GLuint name )
499 {
500    GET_CURRENT_CONTEXT(ctx);
501 
502    if (ctx->RenderMode != GL_SELECT) {
503       return;
504    }
505 
506    if (ctx->Select.NameStackDepth >= MAX_NAME_STACK_DEPTH) {
507       _mesa_error( ctx, GL_STACK_OVERFLOW, "glPushName" );
508       return;
509    }
510 
511    if (!ctx->Const.HardwareAcceleratedSelect || save_used_name_stack(ctx)) {
512       FLUSH_VERTICES(ctx, 0, 0);
513       update_hit_record(ctx);
514    }
515 
516    ctx->Select.NameStack[ctx->Select.NameStackDepth++] = name;
517    ctx->NewState |= _NEW_RENDERMODE;
518 }
519 
520 
521 /**
522  * Pop a name into the name stack.
523  */
524 void GLAPIENTRY
_mesa_PopName(void)525 _mesa_PopName( void )
526 {
527    GET_CURRENT_CONTEXT(ctx);
528 
529    if (ctx->RenderMode != GL_SELECT) {
530       return;
531    }
532 
533    if (ctx->Select.NameStackDepth == 0) {
534       _mesa_error( ctx, GL_STACK_UNDERFLOW, "glPopName" );
535       return;
536    }
537 
538    if (!ctx->Const.HardwareAcceleratedSelect || save_used_name_stack(ctx)) {
539       FLUSH_VERTICES(ctx, 0, 0);
540       update_hit_record(ctx);
541    }
542 
543    ctx->Select.NameStackDepth--;
544    ctx->NewState |= _NEW_RENDERMODE;
545 }
546 
547 /*@}*/
548 
549 
550 /**********************************************************************/
551 /** \name Render Mode */
552 /*@{*/
553 
554 /**
555  * Set rasterization mode.
556  *
557  * \param mode rasterization mode.
558  *
559  * \note this function can't be put in a display list.
560  *
561  * \sa glRenderMode().
562  *
563  * Flushes the vertices and do the necessary cleanup according to the previous
564  * rasterization mode, such as writing the hit record or resent the select
565  * buffer index when exiting the select mode. Updates
566  * __struct gl_contextRec::RenderMode and notifies the driver via the
567  * dd_function_table::RenderMode callback.
568  */
569 GLint GLAPIENTRY
_mesa_RenderMode(GLenum mode)570 _mesa_RenderMode( GLenum mode )
571 {
572    GET_CURRENT_CONTEXT(ctx);
573    GLint result;
574    ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, 0);
575 
576    if (MESA_VERBOSE & VERBOSE_API)
577       _mesa_debug(ctx, "glRenderMode %s\n", _mesa_enum_to_string(mode));
578 
579    FLUSH_VERTICES(ctx, _NEW_RENDERMODE | _NEW_FF_VERT_PROGRAM |
580                   _NEW_FF_FRAG_PROGRAM, 0);
581 
582    switch (ctx->RenderMode) {
583       case GL_RENDER:
584 	 result = 0;
585 	 break;
586       case GL_SELECT:
587 	 save_used_name_stack(ctx);
588 	 update_hit_record(ctx);
589 
590 	 if (ctx->Select.BufferCount > ctx->Select.BufferSize) {
591 	    /* overflow */
592 #ifndef NDEBUG
593             _mesa_warning(ctx, "Feedback buffer overflow");
594 #endif
595 	    result = -1;
596 	 }
597 	 else {
598 	    result = ctx->Select.Hits;
599 	 }
600 	 ctx->Select.BufferCount = 0;
601 	 ctx->Select.Hits = 0;
602 	 /* name stack should be in empty state after exiting GL_SELECT mode */
603 	 reset_name_stack_to_empty(ctx);
604 	 break;
605       case GL_FEEDBACK:
606 	 if (ctx->Feedback.Count > ctx->Feedback.BufferSize) {
607 	    /* overflow */
608 	    result = -1;
609 	 }
610 	 else {
611 	    result = ctx->Feedback.Count;
612 	 }
613 	 ctx->Feedback.Count = 0;
614 	 break;
615       default:
616 	 _mesa_error( ctx, GL_INVALID_ENUM, "glRenderMode" );
617 	 return 0;
618    }
619 
620    switch (mode) {
621       case GL_RENDER:
622          break;
623       case GL_SELECT:
624 	 if (ctx->Select.BufferSize==0) {
625 	    /* haven't called glSelectBuffer yet */
626 	    _mesa_error( ctx, GL_INVALID_OPERATION, "glRenderMode" );
627 	 }
628 	 alloc_select_resource(ctx);
629 	 break;
630       case GL_FEEDBACK:
631 	 if (ctx->Feedback.BufferSize==0) {
632 	    /* haven't called glFeedbackBuffer yet */
633 	    _mesa_error( ctx, GL_INVALID_OPERATION, "glRenderMode" );
634 	 }
635 	 break;
636       default:
637 	 _mesa_error( ctx, GL_INVALID_ENUM, "glRenderMode" );
638 	 return 0;
639    }
640 
641    st_RenderMode( ctx, mode );
642 
643    /* finally update render mode to new one */
644    ctx->RenderMode = mode;
645 
646    return result;
647 }
648 
649 /*@}*/
650 
651 
652 /**********************************************************************/
653 /** \name Initialization */
654 /*@{*/
655 
656 /**
657  * Initialize context feedback data.
658  */
_mesa_init_feedback(struct gl_context * ctx)659 void _mesa_init_feedback( struct gl_context * ctx )
660 {
661    /* Feedback */
662    ctx->Feedback.Type = GL_2D;   /* TODO: verify */
663    ctx->Feedback.Buffer = NULL;
664    ctx->Feedback.BufferSize = 0;
665    ctx->Feedback.Count = 0;
666 
667    /* Selection/picking */
668    ctx->Select.Buffer = NULL;
669    ctx->Select.BufferSize = 0;
670    ctx->Select.BufferCount = 0;
671    ctx->Select.Hits = 0;
672    ctx->Select.NameStackDepth = 0;
673 
674    /* Miscellaneous */
675    ctx->RenderMode = GL_RENDER;
676 }
677 
_mesa_free_feedback(struct gl_context * ctx)678 void _mesa_free_feedback(struct gl_context * ctx)
679 {
680    struct gl_selection *s = &ctx->Select;
681 
682    free(s->SaveBuffer);
683    _mesa_reference_buffer_object(ctx, &s->Result, NULL);
684 }
685 
686 /*@}*/
687