root/OpenSceneGraph/trunk/src/osgViewer/Renderer.cpp @ 8299

Revision 8299, 20.7 kB (checked in by robert, 10 years ago)

Moved compile setup from osgViewer::ViewerBase? into osgViewer::Renderer to
avoid threading issues associated with compile running in a parallel with
update/cull on the first frame.

Also added automatic recompile when a new SceneData? is applied to a View.

Line 
1/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
2 *
3 * This library is open source and may be redistributed and/or modified under 
4 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5 * (at your option) any later version.  The full license is in LICENSE file
6 * included with this distribution, and on the openscenegraph.org website.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * OpenSceneGraph Public License for more details.
12*/
13
14#include <stdio.h>
15
16#include <osg/GLExtensions>
17
18#include <osgUtil/Optimizer>
19#include <osgUtil/GLObjectsVisitor>
20
21#include <osgViewer/Renderer>
22#include <osgViewer/View>
23
24#include <osgDB/DatabasePager>
25
26#include <osg/io_utils>
27
28#include <sstream>
29
30using namespace osgViewer;
31
32//#define DEBUG_MESSAGE osg::notify(osg::NOTICE)
33#define DEBUG_MESSAGE osg::notify(osg::DEBUG_FP)
34
35
36OpenGLQuerySupport::OpenGLQuerySupport():
37    _startTick(0),
38    _initialized(false),
39    _timerQuerySupported(false),
40    _extensions(0),
41    _previousQueryTime(0.0)
42{
43}
44
45void OpenGLQuerySupport::checkQuery(osg::Stats* stats)
46{
47    for(QueryFrameNumberList::iterator itr = _queryFrameNumberList.begin();
48        itr != _queryFrameNumberList.end();
49        )
50    {
51        GLuint query = itr->first;
52        GLint available = 0;
53        _extensions->glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &available);
54        if (available)
55        {
56            GLuint64EXT timeElapsed = 0;
57            _extensions->glGetQueryObjectui64v(query, GL_QUERY_RESULT, &timeElapsed);
58
59            double timeElapsedSeconds = double(timeElapsed)*1e-9;
60            double currentTime = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());
61            double estimatedEndTime = (_previousQueryTime + currentTime) * 0.5;
62            double estimatedBeginTime = estimatedEndTime - timeElapsedSeconds;
63
64            stats->setAttribute(itr->second, "GPU draw begin time", estimatedBeginTime);
65            stats->setAttribute(itr->second, "GPU draw end time", estimatedEndTime);
66            stats->setAttribute(itr->second, "GPU draw time taken", timeElapsedSeconds);
67
68
69            itr = _queryFrameNumberList.erase(itr);
70            _availableQueryObjects.push_back(query);
71        }
72        else
73        {
74            ++itr;
75        }
76
77    }
78    _previousQueryTime = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());
79}
80
81GLuint OpenGLQuerySupport::createQueryObject()
82{
83    if (_availableQueryObjects.empty())
84    {
85        GLuint query;
86        _extensions->glGenQueries(1, &query);
87        return query;
88    }
89    else
90    {
91        GLuint query = _availableQueryObjects.back();
92        _availableQueryObjects.pop_back();
93        return query;
94    }
95}
96
97void OpenGLQuerySupport::beginQuery(int frameNumber)
98{
99    GLuint query = createQueryObject();
100    _extensions->glBeginQuery(GL_TIME_ELAPSED, query);
101    _queryFrameNumberList.push_back(QueryFrameNumberPair(query, frameNumber));       
102}
103
104void OpenGLQuerySupport::endQuery()
105{
106    _extensions->glEndQuery(GL_TIME_ELAPSED);
107}
108
109void OpenGLQuerySupport::initialize(osg::State* state)
110{
111    if (_initialized) return;
112
113    _initialized = true;
114    _extensions = osg::Drawable::getExtensions(state->getContextID(),true);
115    _timerQuerySupported = _extensions && _extensions->isTimerQuerySupported();
116    _previousQueryTime = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());
117}
118
119///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
120//
121//
122//  TheadSafeQueue
123
124Renderer::TheadSafeQueue::TheadSafeQueue()
125{
126    _block.set(false);
127}
128
129Renderer::TheadSafeQueue::~TheadSafeQueue()
130{
131}
132
133osgUtil::SceneView* Renderer::TheadSafeQueue::takeFront()
134{
135    if (_queue.empty()) _block.block();
136
137    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
138    if (_queue.empty()) return 0;
139
140    osgUtil::SceneView* front = _queue.front();
141    _queue.pop_front();
142
143    if (_queue.empty()) _block.set(false);
144   
145    return front;
146}
147
148void Renderer::TheadSafeQueue::add(osgUtil::SceneView* sv)
149{
150    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
151    _queue.push_back(sv);
152    _block.set(true);
153}
154
155static OpenThreads::Mutex s_drawSerializerMutex;
156
157///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
158//
159//
160//  Renderer
161Renderer::Renderer(osg::Camera* camera):
162    osg::GraphicsOperation("Renderer",true),
163    OpenGLQuerySupport(),
164    _targetFrameRate(100.0),
165    _minimumTimeAvailableForGLCompileAndDeletePerFrame(0.001),
166    _flushTimeRatio(0.5),
167    _conservativeTimeRatio(0.5),
168    _camera(camera),
169    _done(false),
170    _graphicsThreadDoesCull(true),
171    _compileOnNextDraw(true)
172{
173
174    DEBUG_MESSAGE<<"Render::Render() "<<this<<std::endl;
175
176    _sceneView[0] = new osgUtil::SceneView;
177    _sceneView[1] = new osgUtil::SceneView;
178
179    unsigned int sceneViewOptions = osgUtil::SceneView::HEADLIGHT;
180
181    osg::Camera* masterCamera = _camera->getView() ? _camera->getView()->getCamera() : camera;
182    osg::StateSet* stateset = masterCamera->getOrCreateStateSet();
183    osgViewer::View* view = dynamic_cast<osgViewer::View*>(_camera->getView());
184
185    osg::DisplaySettings* ds = _camera->getDisplaySettings() ?  _camera->getDisplaySettings() :
186                               ((view && view->getDisplaySettings()) ?  view->getDisplaySettings() :  osg::DisplaySettings::instance());
187
188    _sceneView[0]->setGlobalStateSet(stateset);
189    _sceneView[1]->setGlobalStateSet(stateset);
190   
191    _sceneView[0]->setDefaults(sceneViewOptions);
192    _sceneView[1]->setDefaults(sceneViewOptions);
193
194    _sceneView[0]->setDisplaySettings(ds);
195    _sceneView[1]->setDisplaySettings(ds);
196
197    _sceneView[0]->setCamera(_camera.get(), false);
198    _sceneView[1]->setCamera(_camera.get(), false);
199
200    // lock the mutex for the current cull SceneView to
201    // prevent the draw traversal from reading from it before the cull traversal has been completed.
202    _availableQueue.add(_sceneView[0].get());
203    _availableQueue.add(_sceneView[1].get());
204       
205    DEBUG_MESSAGE<<"_availableQueue.size()="<<_availableQueue._queue.size()<<std::endl;
206
207    _flushOperation = new osg::FlushDeletedGLObjectsOperation(0.1);
208}
209
210Renderer::~Renderer()
211{
212    DEBUG_MESSAGE<<"Render::~Render() "<<this<<std::endl;
213}
214
215void Renderer::setGraphicsThreadDoesCull(bool flag)
216{
217    if (_graphicsThreadDoesCull==flag) return;
218
219    _graphicsThreadDoesCull = flag;
220}
221
222void Renderer::updateSceneView(osgUtil::SceneView* sceneView)
223{
224    osg::Camera* masterCamera = _camera->getView() ? _camera->getView()->getCamera() : _camera.get();
225    osg::StateSet* stateset = masterCamera->getOrCreateStateSet();
226
227    if (sceneView->getGlobalStateSet()!=stateset)
228    {
229        sceneView->setGlobalStateSet(stateset);
230    }
231   
232    osg::GraphicsContext* context = _camera->getGraphicsContext();
233    osg::State* state = context ? context->getState() : 0;
234    if (sceneView->getState()!=state)
235    {
236        sceneView->setState(state);
237    }
238
239    osgViewer::View* view = dynamic_cast<osgViewer::View*>(_camera->getView());
240    osgDB::DatabasePager* databasePager = view ? view->getDatabasePager() : 0;
241    sceneView->getCullVisitor()->setDatabaseRequestHandler(databasePager);
242   
243    sceneView->setFrameStamp(view ? view->getFrameStamp() : state->getFrameStamp());
244   
245    if (databasePager) databasePager->setCompileGLObjectsForContextID(state->getContextID(), true);
246   
247    osg::DisplaySettings* ds = _camera->getDisplaySettings() ?  _camera->getDisplaySettings() :
248                               ((view &&view->getDisplaySettings()) ?  view->getDisplaySettings() :  osg::DisplaySettings::instance());
249
250    sceneView->setDisplaySettings(ds);
251
252    if (view) _startTick = view->getStartTick();
253}
254
255void Renderer::compile()
256{
257    DEBUG_MESSAGE<<"Renderer::compile()"<<std::endl;
258
259    _compileOnNextDraw = false;
260   
261    osgUtil::SceneView* sceneView = _sceneView[0].get();
262    if (!sceneView || _done) return;
263
264    if (sceneView->getSceneData())
265    {
266        osgUtil::GLObjectsVisitor glov;
267        glov.setState(sceneView->getState());
268        sceneView->getSceneData()->accept(glov);
269    }
270}
271
272void Renderer::cull()
273{
274    DEBUG_MESSAGE<<"cull()"<<std::endl;
275
276    if (_done || _graphicsThreadDoesCull) return;
277
278    // note we assume lock has already been acquired.
279    osgUtil::SceneView* sceneView = _availableQueue.takeFront();
280
281    DEBUG_MESSAGE<<"cull() got SceneView "<<sceneView<<std::endl;
282
283    if (sceneView)
284    {
285        updateSceneView(sceneView);
286
287        // osg::notify(osg::NOTICE)<<"Culling buffer "<<_currentCull<<std::endl;
288
289        // pass on the fusion distance settings from the View to the SceneView
290        osgViewer::View* view = dynamic_cast<osgViewer::View*>(sceneView->getCamera()->getView());
291        if (view) sceneView->setFusionDistance(view->getFusionDistanceMode(), view->getFusionDistanceValue());
292
293        osg::Stats* stats = sceneView->getCamera()->getStats();
294        osg::State* state = sceneView->getState();
295        const osg::FrameStamp* fs = state->getFrameStamp();
296        int frameNumber = fs ? fs->getFrameNumber() : 0;
297
298        // do cull traversal
299        osg::Timer_t beforeCullTick = osg::Timer::instance()->tick();
300
301        sceneView->inheritCullSettings(*(sceneView->getCamera()));
302        sceneView->cull();
303
304        osg::Timer_t afterCullTick = osg::Timer::instance()->tick();
305
306#if 0
307        if (sceneView->getDynamicObjectCount()==0 && state->getDynamicObjectRenderingCompletedCallback())
308        {
309            // osg::notify(osg::NOTICE)<<"Completed in cull"<<std::endl;
310            state->getDynamicObjectRenderingCompletedCallback()->completed(state);
311        }
312#endif
313        if (stats && stats->collectStats("rendering"))
314        {
315            DEBUG_MESSAGE<<"Collecting rendering stats"<<std::endl;
316       
317            stats->setAttribute(frameNumber, "Cull traversal begin time", osg::Timer::instance()->delta_s(_startTick, beforeCullTick));
318            stats->setAttribute(frameNumber, "Cull traversal end time", osg::Timer::instance()->delta_s(_startTick, afterCullTick));
319            stats->setAttribute(frameNumber, "Cull traversal time taken", osg::Timer::instance()->delta_s(beforeCullTick, afterCullTick));
320        }
321
322        _drawQueue.add(sceneView);
323
324    }
325
326    DEBUG_MESSAGE<<"end cull() "<<this<<std::endl;
327}
328
329void Renderer::draw()
330{
331    DEBUG_MESSAGE<<"draw() "<<this<<std::endl;
332
333    osg::Timer_t startDrawTick = osg::Timer::instance()->tick();
334
335    osgUtil::SceneView* sceneView = _drawQueue.takeFront();
336
337    DEBUG_MESSAGE<<"draw() got SceneView "<<sceneView<<std::endl;
338
339    osg::GraphicsContext* compileContext = sceneView ? osg::GraphicsContext::getCompileContext(sceneView->getState()->getContextID()) : 0;
340    osg::GraphicsThread* compileThread = compileContext ? compileContext->getGraphicsThread() : 0;
341
342    if (sceneView && !_done)
343    {
344        if (_compileOnNextDraw)
345        {
346            compile();
347        }
348   
349        osgViewer::View* view = dynamic_cast<osgViewer::View*>(_camera->getView());
350        osgDB::DatabasePager* databasePager = view ? view->getDatabasePager() : 0;
351
352        // osg::notify(osg::NOTICE)<<"Drawing buffer "<<_currentDraw<<std::endl;
353
354        if (_done)
355        {
356            osg::notify(osg::INFO)<<"Renderer::release() causing draw to exit"<<std::endl;
357            return;
358        }
359
360        if (_graphicsThreadDoesCull)
361        {
362            osg::notify(osg::INFO)<<"Renderer::draw() completing early due to change in _graphicsThreadDoesCull flag."<<std::endl;
363            return;
364        }
365
366        // osg::notify(osg::NOTICE)<<"RenderingOperation"<<std::endl;
367
368        osg::Stats* stats = sceneView->getCamera()->getStats();
369        osg::State* state = sceneView->getState();
370        int frameNumber = state->getFrameStamp()->getFrameNumber();
371
372        if (!_initialized)
373        {
374            initialize(state);
375        }
376
377        state->setDynamicObjectCount(sceneView->getDynamicObjectCount());
378
379        if (sceneView->getDynamicObjectCount()==0 && state->getDynamicObjectRenderingCompletedCallback())
380        {
381            // osg::notify(osg::NOTICE)<<"Completed in cull"<<std::endl;
382            state->getDynamicObjectRenderingCompletedCallback()->completed(state);
383        }
384
385        bool acquireGPUStats = stats && _timerQuerySupported && stats->collectStats("gpu");
386
387        if (acquireGPUStats)
388        {
389            checkQuery(stats);
390        }
391
392        // do draw traversal
393        if (acquireGPUStats)
394        {
395            checkQuery(stats);
396            beginQuery(frameNumber);
397        }
398
399        osg::Timer_t beforeDrawTick;
400       
401       
402        bool serializeDraw = sceneView->getDisplaySettings()->getSerializeDrawDispatch();
403
404        if (serializeDraw)
405        {
406            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(s_drawSerializerMutex);
407            beforeDrawTick = osg::Timer::instance()->tick();
408            sceneView->draw();
409        }
410        else
411        {
412            beforeDrawTick = osg::Timer::instance()->tick();
413            sceneView->draw();
414        }
415
416        _availableQueue.add(sceneView);
417
418        osg::Timer_t afterDispatchTick = osg::Timer::instance()->tick();
419
420        double dispatchTime = osg::Timer::instance()->delta_s(beforeDrawTick, afterDispatchTick);
421
422        // now flush delete OpenGL objects and compile any objects as required by the DatabasePager
423        flushAndCompile(dispatchTime, sceneView, databasePager, compileThread);
424   
425        if (acquireGPUStats)
426        {
427            endQuery();
428            checkQuery(stats);
429        }
430
431        //glFlush();
432       
433        osg::Timer_t afterDrawTick = osg::Timer::instance()->tick();
434
435//        osg::notify(osg::NOTICE)<<"Time wait for draw = "<<osg::Timer::instance()->delta_m(startDrawTick, beforeDrawTick)<<std::endl;
436//        osg::notify(osg::NOTICE)<<"     time for draw = "<<osg::Timer::instance()->delta_m(beforeDrawTick, afterDrawTick)<<std::endl;
437
438        if (stats && stats->collectStats("rendering"))
439        {
440            stats->setAttribute(frameNumber, "Draw traversal begin time", osg::Timer::instance()->delta_s(_startTick, beforeDrawTick));
441            stats->setAttribute(frameNumber, "Draw traversal end time", osg::Timer::instance()->delta_s(_startTick, afterDrawTick));
442            stats->setAttribute(frameNumber, "Draw traversal time taken", osg::Timer::instance()->delta_s(beforeDrawTick, afterDrawTick));
443        }
444    }
445
446    DEBUG_MESSAGE<<"end draw() "<<this<<std::endl;
447}
448
449void Renderer::cull_draw()
450{
451    DEBUG_MESSAGE<<"cull_draw() "<<this<<std::endl;
452
453    osgUtil::SceneView* sceneView = _sceneView[0].get();
454    if (!sceneView || _done) return;
455
456    if (_compileOnNextDraw)
457    {
458        compile();
459    }
460
461    updateSceneView(sceneView);
462
463    osgViewer::View* view = dynamic_cast<osgViewer::View*>(_camera->getView());
464    osgDB::DatabasePager* databasePager = view ? view->getDatabasePager() : 0;
465
466    osg::GraphicsContext* compileContext = osg::GraphicsContext::getCompileContext(sceneView->getState()->getContextID());
467    osg::GraphicsThread* compileThread = compileContext ? compileContext->getGraphicsThread() : 0;
468
469    if (_done)
470    {
471        osg::notify(osg::INFO)<<"Render::release() causing cull_draw to exit"<<std::endl;
472        return;
473    }
474
475    // osg::notify(osg::NOTICE)<<"RenderingOperation"<<std::endl;
476
477    // pass on the fusion distance settings from the View to the SceneView
478    if (view) sceneView->setFusionDistance(view->getFusionDistanceMode(), view->getFusionDistanceValue());
479
480    osg::Stats* stats = sceneView->getCamera()->getStats();
481    osg::State* state = sceneView->getState();
482    const osg::FrameStamp* fs = state->getFrameStamp();
483    int frameNumber = fs ? fs->getFrameNumber() : 0;
484
485    if (!_initialized)
486    {
487        initialize(state);
488    }
489
490    bool acquireGPUStats = stats && _timerQuerySupported && stats->collectStats("gpu");
491
492    if (acquireGPUStats)
493    {
494        checkQuery(stats);
495    }
496
497    // do cull traversal
498    osg::Timer_t beforeCullTick = osg::Timer::instance()->tick();
499
500    sceneView->inheritCullSettings(*(sceneView->getCamera()));
501    sceneView->cull();
502
503    osg::Timer_t afterCullTick = osg::Timer::instance()->tick();
504
505#if 0
506    if (state->getDynamicObjectCount()==0 && state->getDynamicObjectRenderingCompletedCallback())
507    {
508        state->getDynamicObjectRenderingCompletedCallback()->completed(state);
509    }
510#endif
511
512
513    // do draw traversal
514    if (acquireGPUStats)
515    {
516        checkQuery(stats);
517        beginQuery(frameNumber);
518    }
519
520    osg::Timer_t beforeDrawTick;
521
522    bool serializeDraw = sceneView->getDisplaySettings()->getSerializeDrawDispatch();
523
524    if (serializeDraw)
525    {
526        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(s_drawSerializerMutex);
527       
528        beforeDrawTick = osg::Timer::instance()->tick();
529        sceneView->draw();
530    }
531    else
532    {
533        beforeDrawTick = osg::Timer::instance()->tick();
534        sceneView->draw();
535    }
536
537    osg::Timer_t afterDispatchTick = osg::Timer::instance()->tick();
538    double cullAndDispatchTime = osg::Timer::instance()->delta_s(beforeCullTick, afterDispatchTick);
539
540    // now flush delete OpenGL objects and compile any objects as required by the DatabasePager
541    flushAndCompile(cullAndDispatchTime, sceneView, databasePager, compileThread);
542
543
544    if (acquireGPUStats)
545    {
546        endQuery();
547        checkQuery(stats);
548    }
549
550    osg::Timer_t afterDrawTick = osg::Timer::instance()->tick();
551
552    if (stats && stats->collectStats("rendering"))
553    {
554        DEBUG_MESSAGE<<"Collecting rendering stats"<<std::endl;
555
556        stats->setAttribute(frameNumber, "Cull traversal begin time", osg::Timer::instance()->delta_s(_startTick, beforeCullTick));
557        stats->setAttribute(frameNumber, "Cull traversal end time", osg::Timer::instance()->delta_s(_startTick, afterCullTick));
558        stats->setAttribute(frameNumber, "Cull traversal time taken", osg::Timer::instance()->delta_s(beforeCullTick, afterCullTick));
559
560        stats->setAttribute(frameNumber, "Draw traversal begin time", osg::Timer::instance()->delta_s(_startTick, beforeDrawTick));
561        stats->setAttribute(frameNumber, "Draw traversal end time", osg::Timer::instance()->delta_s(_startTick, afterDrawTick));
562        stats->setAttribute(frameNumber, "Draw traversal time taken", osg::Timer::instance()->delta_s(beforeDrawTick, afterDrawTick));
563    }
564
565    DEBUG_MESSAGE<<"end cull_draw() "<<this<<std::endl;
566
567}
568
569void Renderer::flushAndCompile(double currentElapsedFrameTime, osgUtil::SceneView* sceneView, osgDB::DatabasePager* databasePager, osg::GraphicsThread* compileThread)
570{
571   
572    double targetFrameRate = _targetFrameRate;
573    double minimumTimeAvailableForGLCompileAndDeletePerFrame = _minimumTimeAvailableForGLCompileAndDeletePerFrame;
574
575    if (databasePager)
576    {
577        targetFrameRate = std::min(targetFrameRate, databasePager->getTargetFrameRate());
578        minimumTimeAvailableForGLCompileAndDeletePerFrame = std::min(minimumTimeAvailableForGLCompileAndDeletePerFrame, databasePager->getMinimumTimeAvailableForGLCompileAndDeletePerFrame());
579    }
580   
581    double targetFrameTime = 1.0/targetFrameRate;
582
583    double availableTime = std::max((targetFrameTime - currentElapsedFrameTime)*_conservativeTimeRatio,
584                                    minimumTimeAvailableForGLCompileAndDeletePerFrame);
585
586    double flushTime = availableTime * _flushTimeRatio;
587    double compileTime = availableTime - flushTime;
588
589#if 0
590    osg::notify(osg::NOTICE)<<"total availableTime = "<<availableTime*1000.0<<std::endl;
591    osg::notify(osg::NOTICE)<<"      flushTime     = "<<flushTime*1000.0<<std::endl;
592    osg::notify(osg::NOTICE)<<"      compileTime   = "<<compileTime*1000.0<<std::endl;
593#endif
594
595    if (compileThread)
596    {
597        compileThread->add(_flushOperation.get());
598    }
599    else
600    {
601        sceneView->flushDeletedGLObjects(flushTime);
602    }
603
604    // if any time left over from flush add this to compile time.       
605    if (flushTime>0.0) compileTime += flushTime;
606
607#if 0
608    osg::notify(osg::NOTICE)<<"      revised compileTime   = "<<compileTime*1000.0<<std::endl;
609#endif
610
611    if (databasePager && databasePager->requiresExternalCompileGLObjects(sceneView->getState()->getContextID()))
612    {
613        databasePager->compileGLObjects(*(sceneView->getState()), compileTime);
614    }
615}
616
617void Renderer::operator () (osg::Object* object)
618{
619    osg::GraphicsContext* context = dynamic_cast<osg::GraphicsContext*>(object);
620    if (context) operator()(context);
621
622    osg::Camera* camera = dynamic_cast<osg::Camera*>(object);
623    if (camera) cull();
624}
625
626void Renderer::operator () (osg::GraphicsContext* context)
627{
628    if (_graphicsThreadDoesCull)
629    {
630        cull_draw();
631    }
632    else
633    {
634        draw();
635    }
636}
637
638void Renderer::release()
639{
640    osg::notify(osg::INFO)<<"Renderer::release()"<<std::endl;
641    _done = true;
642
643    _availableQueue.release();
644    _drawQueue.release();
645}
Note: See TracBrowser for help on using the browser.