source: nanovis/tags/1.2.2/VelocityArrowsSlice.cpp @ 6369

Last change on this file since 6369 was 5394, checked in by ldelgass, 9 years ago

Merge r3875:3876 from trunk, update mergeinfo

  • Property svn:eol-style set to native
File size: 18.8 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  HUBzero Foundation, LLC
4 *
5 */
6
7#include <math.h>
8
9#include <GL/glew.h>
10#include <GL/gl.h>
11
12#include <vrmath/Vector3f.h>
13#include <util/FilePath.h>
14#include <Image.h>
15#include <ImageLoaderFactory.h>
16#include <ImageLoader.h>
17
18#include "nanovis.h"
19#include "VelocityArrowsSlice.h"
20#include "Shader.h"
21#include "Camera.h"
22
23using namespace nv;
24using namespace nv::util;
25using namespace vrmath;
26
27static inline float deg2rad(float deg)
28{
29    return ((deg * M_PI) / 180.);
30}
31
32static inline float rad2deg(float rad)
33{
34    return ((rad * 180.) / M_PI);
35}
36
37VelocityArrowsSlice::VelocityArrowsSlice() :
38    _vectorFieldGraphicsID(0),
39    _slicePos(0.5f),
40    _axis(AXIS_Z),
41    _fbo(0),
42    _tex(0),
43    _maxPointSize(1.0f),
44    _renderTargetWidth(128),
45    _renderTargetHeight(128),
46    _velocities(NULL),
47    _projectionVector(1, 1, 0),
48    _tickCountForMinSizeAxis(10),
49    _tickCountX(0),
50    _tickCountY(0),
51    _tickCountZ(0),
52    _pointCount(0),
53    _maxVelocityScale(1, 1, 1),
54    _arrowColor(1, 1, 0),
55    _visible(false),
56    _dirty(true),
57    _vertexBufferGraphicsID(0),
58    _arrowsTex(NULL),
59    _renderMode(LINES)
60{
61    setSliceAxis(_axis);
62
63    _queryVelocityFP.loadFragmentProgram("queryvelocity.cg");
64
65    // Delay loading of shaders only required for glyph style rendering
66    if (_renderMode == GLYPHS) {
67        _particleShader.loadVertexProgram("velocityslicevp.cg");
68        _particleShader.loadFragmentProgram("velocityslicefp.cg");
69    }
70
71    createRenderTarget();
72
73    std::string path = FilePath::getInstance()->getPath("arrows.bmp");
74    if (!path.empty()) {
75        ImageLoader *loader = ImageLoaderFactory::getInstance()->createLoader("bmp");
76        if (loader != NULL) {
77            Image *image = loader->load(path.c_str(), Image::IMG_RGBA);
78            if (image != NULL) {
79                unsigned char *bytes = (unsigned char *)image->getImageBuffer();
80                if (bytes != NULL) {
81                    _arrowsTex = new Texture2D(image->getWidth(), image->getHeight(),
82                                               GL_UNSIGNED_BYTE, GL_LINEAR, 4, NULL);
83                    _arrowsTex->setWrapS(GL_MIRRORED_REPEAT);
84                    _arrowsTex->setWrapT(GL_MIRRORED_REPEAT);
85                    _arrowsTex->initialize(image->getImageBuffer());
86                }
87                delete image;
88            } else {
89                ERROR("Failed to load arrows image");
90            }
91            delete loader;
92        } else {
93            ERROR("Couldn't find loader for arrows image");
94        }
95    } else {
96        ERROR("Couldn't find arrows image");
97    }
98
99    GLfloat minMax[2];
100    glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, minMax);
101    TRACE("Aliased point size range: %g %g", minMax[0], minMax[1]);
102    _maxPointSize = minMax[1];
103    glGetFloatv(GL_SMOOTH_POINT_SIZE_RANGE, minMax);
104    TRACE("Smooth point size range: %g %g", minMax[0], minMax[1]);
105    _maxPointSize = minMax[1] > _maxPointSize ? minMax[1] : _maxPointSize;
106    TRACE("Max point size: %g", _maxPointSize);
107
108    TRACE("Leaving VelocityArrowsSlice constructor");
109}
110
111VelocityArrowsSlice::~VelocityArrowsSlice()
112{
113    if (_tex != 0) {
114        glDeleteTextures(1, &_tex);
115    }
116    if (_fbo != 0) {
117        glDeleteFramebuffersEXT(1, &_fbo);
118    }
119    if (_arrowsTex != NULL) {
120        delete _arrowsTex;
121    }
122    if (_vertexBufferGraphicsID != 0) {
123        glDeleteBuffers(1, &_vertexBufferGraphicsID);
124    }
125    if (_velocities != NULL) {
126        delete [] _velocities;
127    }
128}
129
130void VelocityArrowsSlice::createRenderTarget()
131{
132    if (_velocities != NULL) {
133        delete [] _velocities;
134    }
135    _velocities = new Vector3f[_renderTargetWidth * _renderTargetHeight];
136
137    if (_vertexBufferGraphicsID != 0) {
138        glDeleteBuffers(1, &_vertexBufferGraphicsID);
139    }
140
141    glGenBuffers(1, &_vertexBufferGraphicsID);
142    glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vertexBufferGraphicsID);
143    glBufferDataARB(GL_ARRAY_BUFFER_ARB, _renderTargetWidth * _renderTargetHeight * 3 * sizeof(float),
144                    0, GL_DYNAMIC_DRAW_ARB);
145    glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
146
147    if (_tex != 0) {
148        glDeleteTextures(1, &_tex);
149    }
150    if (_fbo != 0) {
151        glDeleteFramebuffersEXT(1, &_fbo);
152    }
153
154    glGenFramebuffersEXT(1, &_fbo);
155    glGenTextures(1, &_tex);
156    int fboOrig;
157    glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
158    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
159
160    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _tex);
161    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
162    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
163    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
164    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
165
166    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA32F_ARB,
167                 _renderTargetWidth, _renderTargetHeight, 0,
168                 GL_RGBA, GL_FLOAT, NULL);
169
170    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
171                              GL_TEXTURE_RECTANGLE_ARB, _tex, 0);
172
173    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
174}
175
176void VelocityArrowsSlice::setSliceAxis(FlowSliceAxis axis)
177{
178    _axis = axis;
179    switch (_axis) {
180    case AXIS_X:
181        _projectionVector.x = 0;
182        _projectionVector.y = 1;
183        _projectionVector.z = 1;
184        break;
185    case AXIS_Y:
186        _projectionVector.x = 1;
187        _projectionVector.y = 0;
188        _projectionVector.z = 1;
189        break;
190    case AXIS_Z:
191        _projectionVector.x = 1;
192        _projectionVector.y = 1;
193        _projectionVector.z = 0;
194        break;
195    }
196    _dirty = true;
197}
198
199void VelocityArrowsSlice::queryVelocity()
200{
201    glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT);
202    int fboOrig;
203    glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
204
205    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
206
207    _queryVelocityFP.bind();
208    _queryVelocityFP.setFPTextureParameter("vfield", _vectorFieldGraphicsID);
209
210    glDisable(GL_DEPTH_TEST);
211    glViewport(0, 0, _renderTargetWidth, _renderTargetHeight);
212    glMatrixMode(GL_PROJECTION);
213    glPushMatrix();
214    glLoadIdentity();
215    glOrtho(0, _renderTargetWidth, 0, _renderTargetHeight, -10.0f, 10.0f);
216    glMatrixMode(GL_MODELVIEW);
217    glPushMatrix();
218    glLoadIdentity();
219
220    glBegin(GL_QUADS);
221    switch (_axis) {
222    case AXIS_X:
223        glTexCoord3f(_slicePos, 0, 0); glVertex2i(0,                  0);
224        glTexCoord3f(_slicePos, 1, 0); glVertex2i(_renderTargetWidth, 0);
225        glTexCoord3f(_slicePos, 1, 1); glVertex2i(_renderTargetWidth, _renderTargetHeight);
226        glTexCoord3f(_slicePos, 0, 1); glVertex2i(0,                  _renderTargetHeight);
227        break;
228    case AXIS_Y:
229        glTexCoord3f(0, _slicePos, 0); glVertex2i(0,                  0);
230        glTexCoord3f(1, _slicePos, 0); glVertex2i(_renderTargetWidth, 0);
231        glTexCoord3f(1, _slicePos, 1); glVertex2i(_renderTargetWidth, _renderTargetHeight);
232        glTexCoord3f(0, _slicePos, 1); glVertex2i(0,                  _renderTargetHeight);
233        break;
234    case AXIS_Z:
235    default:
236        glTexCoord3f(0, 0, _slicePos); glVertex2i(0,                  0);
237        glTexCoord3f(1, 0, _slicePos); glVertex2i(_renderTargetWidth, 0);
238        glTexCoord3f(1, 1, _slicePos); glVertex2i(_renderTargetWidth, _renderTargetHeight);
239        glTexCoord3f(0, 1, _slicePos); glVertex2i(0,                  _renderTargetHeight);
240        break;
241    }
242    glEnd();
243
244    _queryVelocityFP.disableFPTextureParameter("vfield");
245    _queryVelocityFP.unbind();
246
247    glReadPixels(0, 0, _renderTargetWidth, _renderTargetHeight, GL_RGB, GL_FLOAT, _velocities);
248
249    glMatrixMode(GL_PROJECTION);
250    glPopMatrix();
251    glMatrixMode(GL_MODELVIEW);
252    glPopMatrix();
253
254    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
255
256    glPopAttrib();
257}
258
259static void drawLineArrow(FlowSliceAxis axis)
260{
261    glBegin(GL_LINES);
262    switch (axis) {
263    case AXIS_X: // ZY plane
264        glVertex3f(0.0, 0.0, 0.0);
265        glVertex3f(0.0, 0.0, 1.0);
266
267        glVertex3f(0.0, 0.0, 1.0);
268        glVertex3f(0.0, 0.25, 0.75);
269
270        glVertex3f(0.0, 0.0, 1.0);
271        glVertex3f(0.0, -0.25, 0.75);
272        break;
273    case AXIS_Y: // XZ plane
274        glVertex3f(0.0, 0.0, 0.0);
275        glVertex3f(1.0, 0.0, 0.0);
276
277        glVertex3f(1.0, 0.0, 0.0);
278        glVertex3f(0.75, 0.0, 0.25);
279
280        glVertex3f(1.0, 0.0, 0.0);
281        glVertex3f(0.75, 0.0, -0.25);
282        break;
283    case AXIS_Z: // XY plane
284    default:
285        glVertex3f(0.0, 0.0, 0.0);
286        glVertex3f(1.0, 0.0, 0.0);
287
288        glVertex3f(1.0, 0.0, 0.0);
289        glVertex3f(0.75, 0.25, 0.0);
290
291        glVertex3f(1.0, 0.0, 0.0);
292        glVertex3f(0.75, -0.25, 0.0);
293        break;
294    }
295    glEnd();
296}
297
298void VelocityArrowsSlice::render()
299{
300    if (!_visible)
301        return;
302
303    if (_dirty) {
304        computeSamplingTicks();
305        queryVelocity();
306        _dirty = false;
307    }
308
309    TRACE("_scale: %g %g %g", _scale.x, _scale.y, _scale.z);
310    TRACE("_maxVelocityScale: %g %g %g",
311          _maxVelocityScale.x, _maxVelocityScale.y, _maxVelocityScale.z);
312
313    glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
314
315    glMatrixMode(GL_MODELVIEW);
316    glPushMatrix();
317
318    glScalef(_scale.x, _scale.y, _scale.z);
319    glTranslatef(-0.5f, -0.5f, -0.5f);
320
321    if (_renderMode == LINES) {
322        glDisable(GL_TEXTURE_2D);
323        glDisable(GL_LIGHTING);
324        glLineWidth(2.0);
325        glColor3f(_arrowColor.x, _arrowColor.y, _arrowColor.z);
326 
327        Vector3f pos;
328        Vector3f vel;
329        Vector3f refVec;
330        Vector3f blue(0, 0, 1);
331        Vector3f red(1, 0, 0);
332
333        int index = 0, icount, jcount;
334        switch (_axis) {
335        case AXIS_X:
336            icount = _tickCountZ;
337            jcount = _tickCountY;
338            refVec.set(0, 0, 1);
339            break;
340        case AXIS_Y:
341            icount = _tickCountZ;
342            jcount = _tickCountX;
343            refVec.set(1, 0, 0);
344            break;
345        case AXIS_Z:
346        default:
347            icount = _tickCountY;
348            jcount = _tickCountX;
349            refVec.set(1, 0, 0);
350            break;
351        }
352
353        for (int i = 0; i < icount; ++i) {
354            for (int j = 0; j < jcount; ++j, ++index) {
355                pos = _samplingPositions[index];
356                // Normalized velocity: [-1,1] components
357                // Project 3D vector onto sample plane
358                vel = _velocities[index].scale(_projectionVector);
359                // Length: [0,1]
360                double length = vel.length();
361                if (length < 0.0 || length > 1.0) {
362                    TRACE("***vec: (%g, %g, %g) length: %g***", vel.x, vel.y, vel.z, length);
363                    continue;
364                }
365                if (length > 0.0) {
366                    Vector3f vnorm = vel.normalize();
367                    Vector3f rotationAxis = refVec.cross(vnorm);
368                    double angle = rad2deg(acos(refVec.dot(vnorm)));
369                    Vector3f color = blue * (1.0 - length) + red * length;
370                    float scale = length;
371                    if (scale < 0.10) scale = 0.10;
372                    glMatrixMode(GL_MODELVIEW);
373                    glPushMatrix();
374                    glColor3f(color.x, color.y, color.z);
375                    glTranslatef(pos.x, pos.y, pos.z);
376                    glScalef(2.0 * _maxVelocityScale.x,
377                             2.0 * _maxVelocityScale.y,
378                             2.0 * _maxVelocityScale.z);
379                    glScalef(scale, scale, scale);
380                    if (angle > 1.0e-6 || angle < -1.0e-6)
381                        glRotated(angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
382                    drawLineArrow(_axis);
383                    glPopMatrix();
384                }
385            }
386        }
387
388        glLineWidth(1.0);
389    } else {
390        glColor4f(_arrowColor.x, _arrowColor.y, _arrowColor.z, 1.0f);
391        glEnable(GL_DEPTH_TEST);
392        glDisable(GL_LIGHTING);
393#if 0
394        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
395        glEnable(GL_BLEND);
396        glDepthMask(GL_FALSE);
397#else
398        glDisable(GL_BLEND);
399#endif
400        glAlphaFunc(GL_GREATER, 0.6);
401        glEnable(GL_ALPHA_TEST);
402        glEnable(GL_POINT_SPRITE_ARB);
403        glPointSize(20);
404        glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
405
406        _arrowsTex->activate();
407        glEnable(GL_TEXTURE_2D);
408        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
409
410        GLfloat atten[] = {1, 0, 0};
411        glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, atten);
412        glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 0.0f);
413        glPointParameterfARB(GL_POINT_SIZE_MIN_ARB, 1.0f);
414        glPointParameterfARB(GL_POINT_SIZE_MAX_ARB, _maxPointSize);
415        glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
416
417        // FIXME: This vertex shader won't compile with ARB_vertex_program,
418        // so it should use GLSL
419        if (!_particleShader.isVertexProgramLoaded()) {
420            _particleShader.loadVertexProgram("velocityslicevp.cg");
421        }
422        if (!_particleShader.isFragmentProgramLoaded()) {
423            _particleShader.loadFragmentProgram("velocityslicefp.cg");
424        }
425
426        _particleShader.bind();
427        _particleShader.setVPTextureParameter("vfield", _vectorFieldGraphicsID);
428        _particleShader.setFPTextureParameter("arrows", _arrowsTex->id());
429        _particleShader.setVPParameter1f("tanHalfFOV",
430                                         tan(NanoVis::getCamera()->getFov() * 0.5) * NanoVis::winHeight * 0.5);
431        _particleShader.setGLStateMatrixVPParameter("modelview",
432                                                    Shader::MODELVIEW_MATRIX,
433                                                    Shader::MATRIX_IDENTITY);
434        _particleShader.setGLStateMatrixVPParameter("mvp",
435                                                    Shader::MODELVIEW_PROJECTION_MATRIX,
436                                                    Shader::MATRIX_IDENTITY);
437
438        glEnableClientState(GL_VERTEX_ARRAY);
439        glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vertexBufferGraphicsID);
440        glVertexPointer(3, GL_FLOAT, 0, 0);
441        //glEnableClientState(GL_COLOR_ARRAY);
442
443        // TBD..
444        glDrawArrays(GL_POINTS, 0, _pointCount);
445        glPointSize(1);
446
447        glDisableClientState(GL_VERTEX_ARRAY);
448        glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
449        glDisable(GL_POINT_SPRITE_ARB);
450
451        _particleShader.disableVPTextureParameter("vfield");
452        _particleShader.disableFPTextureParameter("arrows");
453        _particleShader.unbind();
454
455        glActiveTexture(GL_TEXTURE0);
456        glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_FALSE);
457        _arrowsTex->deactivate();
458    }
459
460    glPopMatrix();
461    glPopAttrib();
462}
463
464void
465VelocityArrowsSlice::setVectorField(unsigned int vfGraphicsID, const Vector3f& origin,
466                                    float xScale, float yScale, float zScale, float max)
467{
468    _vectorFieldGraphicsID = vfGraphicsID;
469    _scale.set(xScale, yScale, zScale);
470
471    _dirty = true;
472}
473
474void VelocityArrowsSlice::computeSamplingTicks()
475{
476    if (_scale.x < _scale.y) {
477        if (_scale.x < _scale.z || _scale.z == 0.0) {
478            // _scale.x
479            _tickCountX = _tickCountForMinSizeAxis;
480
481            float step = _scale.x / (_tickCountX + 1);
482            _tickCountY = (int)(_scale.y/step);
483            _tickCountZ = (int)(_scale.z/step);
484        } else {
485            // _scale.z
486            _tickCountZ = _tickCountForMinSizeAxis;
487
488            float step = _scale.z / (_tickCountZ + 1);
489            _tickCountX = (int)(_scale.x/step);
490            _tickCountY = (int)(_scale.y/step);
491        }
492    } else {
493        if (_scale.y < _scale.z || _scale.z == 0.0) {
494            // _scale.y
495            _tickCountY = _tickCountForMinSizeAxis;
496
497            float step = _scale.y / (_tickCountY + 1);
498            _tickCountX = (int)(_scale.x/step);
499            _tickCountZ = (int)(_scale.z/step);
500        } else {
501            // _scale.z
502            _tickCountZ = _tickCountForMinSizeAxis;
503
504            float step = _scale.z / (_tickCountZ + 1);
505            _tickCountX = (int)(_scale.x/step);
506            _tickCountY = (int)(_scale.y/step);
507        }
508    }
509
510    switch (_axis) {
511    case AXIS_X:
512        _tickCountX = 1;
513        _renderTargetWidth = _tickCountY;
514        _renderTargetHeight = _tickCountZ;
515        break;
516    case AXIS_Y:
517        _tickCountY = 1;
518        _renderTargetWidth = _tickCountX;
519        _renderTargetHeight = _tickCountZ;
520        break;
521    default:
522    case AXIS_Z:
523        _tickCountZ = 1;
524        _renderTargetWidth = _tickCountX;
525        _renderTargetHeight = _tickCountY;
526        break;
527    }
528
529    _maxVelocityScale.x = (1.0f / _tickCountX) * 0.8f;
530    _maxVelocityScale.y = (1.0f / _tickCountY) * 0.8f;
531    _maxVelocityScale.z = (1.0f / _tickCountZ) * 0.8f;
532
533    TRACE("Tick counts: %d %d %d", _tickCountX, _tickCountY, _tickCountZ);
534
535    int pointCount = _tickCountX * _tickCountY * _tickCountZ;
536    if (_pointCount != pointCount) {
537        _samplingPositions.clear();
538        _samplingPositions.reserve(pointCount);
539        _pointCount = pointCount;
540    }
541
542    createRenderTarget();
543
544    Vector3f pos;
545    Vector3f *pinfo = NULL;
546    if (_renderMode == GLYPHS) {
547        glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vertexBufferGraphicsID);
548        pinfo = (Vector3f *)glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
549    }
550
551    if (_axis == AXIS_Z) {
552        for (int y = 1; y <= _tickCountY; ++y) {
553            for (int x = 1; x <= _tickCountX; ++x) {
554                pos.x = (1.0f / (_tickCountX + 1)) * x;
555                pos.y = (1.0f / (_tickCountY + 1)) * y;
556                pos.z = _slicePos;
557                if (_renderMode == LINES) {
558                    _samplingPositions.push_back(pos);
559                } else {
560                    *pinfo = pos;
561                    ++pinfo;
562                }
563            }
564        }
565    } else if (_axis == AXIS_Y) {
566        for (int z = 1; z <= _tickCountZ; ++z) {
567            for (int x = 1; x <= _tickCountX; ++x) {
568                pos.x = (1.0f / (_tickCountX + 1)) * x;
569                pos.y = _slicePos;
570                pos.z = (1.0f / (_tickCountZ + 1)) * z;
571                if (_renderMode == LINES) {
572                    _samplingPositions.push_back(pos);
573                } else {
574                    *pinfo = pos;
575                    ++pinfo;
576                }
577            }
578        }
579    } else if (_axis == AXIS_X) {
580        for (int z = 1; z <= _tickCountZ; ++z) {
581            for (int y = 1; y <= _tickCountY; ++y) {
582                pos.x = _slicePos;
583                pos.y = (1.0f / (_tickCountY + 1)) * y;
584                pos.z = (1.0f / (_tickCountZ + 1)) * z;
585                if (_renderMode == LINES) {
586                    _samplingPositions.push_back(pos);
587                } else {
588                    *pinfo = pos;
589                    ++pinfo;
590                }
591            }
592        }
593    }
594
595    if (_renderMode == GLYPHS) {
596        glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
597    }
598}
Note: See TracBrowser for help on using the repository browser.