2
// Copyright (C) 2007 Skew Matrix Software LLC (http://www.skew-matrix.com)
4
// This library is open source and may be redistributed and/or modified under
5
// the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
6
// (at your option) any later version. The full license is in LICENSE file
7
// included with this distribution, and on the openscenegraph.org website.
9
// This library is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
// OpenSceneGraph Public License for more details.
15
#include <osg/OcclusionQueryNode>
16
#include <OpenThreads/ScopedLock>
21
#include <osg/MatrixTransform>
24
#include <osg/Geometry>
25
#include <osg/BoundingBox>
26
#include <osg/BoundingSphere>
27
#include <osg/Referenced>
28
#include <osg/ComputeBoundsVisitor>
29
#include <osg/StateSet>
30
#include <osg/StateAttribute>
31
#include <osg/PolygonMode>
32
#include <osg/ColorMask>
33
#include <osg/PolygonOffset>
39
typedef osg::buffered_value< osg::ref_ptr< osg::Drawable::Extensions > > OcclusionQueryBufferedExtensions;
40
static OcclusionQueryBufferedExtensions s_OQ_bufferedExtensions;
43
// Support classes, used by (and private to) OcclusionQueryNode.
44
// (Note a lot of this is historical. OcclusionQueryNode formaerly
45
// existed as a NodeKit outside the core OSG distribution. Many
46
// of these classes existed in their own separate header and
50
// Create and return a StateSet appropriate for performing an occlusion
51
// query test (disable lighting, texture mapping, etc). Probably some
52
// room for improvement here. Could disable shaders, for example.
56
osg::StateSet* state = new osg::StateSet;
57
// TBD Possible bug, need to allow user to set render bin number.
58
state->setRenderBinDetails( 9, "RenderBin" );
60
state->setMode( GL_LIGHTING, osg::StateAttribute::OFF |
61
osg::StateAttribute::PROTECTED);
62
state->setTextureMode( 0, GL_TEXTURE_2D, osg::StateAttribute::OFF |
63
osg::StateAttribute::PROTECTED);
64
state->setMode( GL_CULL_FACE, osg::StateAttribute::ON |
65
osg::StateAttribute::PROTECTED);
67
osg::ColorMask* cm = new osg::ColorMask( false, false, false, false );
68
state->setAttributeAndModes( cm, osg::StateAttribute::ON |
69
osg::StateAttribute::PROTECTED);
70
osg::Depth* d = new osg::Depth( osg::Depth::LEQUAL, 0.f, 1.f, false );
71
state->setAttributeAndModes( d, osg::StateAttribute::ON |
72
osg::StateAttribute::PROTECTED);
73
osg::PolygonMode* pm = new osg::PolygonMode(
74
osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL );
75
state->setAttributeAndModes( pm, osg::StateAttribute::ON |
76
osg::StateAttribute::PROTECTED);
78
osg::PolygonOffset* po = new osg::PolygonOffset( -1., -1. );
79
state->setAttributeAndModes( po, osg::StateAttribute::ON |
80
osg::StateAttribute::PROTECTED);
85
// Create and return a StateSet for rendering a debug representation of query geometry.
89
osg::StateSet* debugState = new osg::StateSet;
91
debugState->setMode( GL_LIGHTING, osg::StateAttribute::OFF |
92
osg::StateAttribute::PROTECTED);
93
debugState->setTextureMode( 0, GL_TEXTURE_2D, osg::StateAttribute::OFF |
94
osg::StateAttribute::PROTECTED);
95
debugState->setMode( GL_CULL_FACE, osg::StateAttribute::ON |
96
osg::StateAttribute::PROTECTED);
98
osg::PolygonMode* pm = new osg::PolygonMode(
99
osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE );
100
debugState->setAttributeAndModes( pm, osg::StateAttribute::ON |
101
osg::StateAttribute::PROTECTED);
103
osg::PolygonOffset* po = new osg::PolygonOffset( -1., -1. );
104
debugState->setAttributeAndModes( po, osg::StateAttribute::ON |
105
osg::StateAttribute::PROTECTED);
111
// TestResult -- stores (per context) results of an occlusion query
112
// test performed by QueryGeometry. An OcclusionQueryNode has a
113
// Geode owning a single QueryGeometry that
114
// draws the occlusion query geometry. QueryGeometry keeps a
115
// TestResult per context to store the result/status of each query.
116
// Accessed during the cull and draw traversals.
117
class TestResult : public osg::Referenced
120
TestResult() : _init( false ), _id( 0 ), _contextID( 0 ), _active( false ), _numPixels( 0 ) {}
125
// Query ID for this context.
127
// Context ID owning this query ID.
128
unsigned int _contextID;
130
// Set to true when a query gets issued and set to
131
// false when the result is retrieved.
132
mutable bool _active;
134
// Result of last query.
138
// QueryGeometry -- A Drawable that performs an occlusion query,
139
// using its geometric data as the query geometry.
140
class QueryGeometry : public osg::Geometry
143
QueryGeometry( const std::string& oqnName=std::string("") );
148
// TBD implement copy constructor
150
virtual void drawImplementation( osg::RenderInfo& renderInfo ) const;
152
unsigned int getNumPixels( const osg::Camera* cam );
155
void releaseGLObjects( osg::State* state = 0 );
156
static void deleteQueryObject( unsigned int contextID, GLuint handle );
157
static void flushDeletedQueryObjects( unsigned int contextID, double currentTime, double& availableTime );
158
static void discardDeletedQueryObjects( unsigned int contextID );
161
typedef std::map< const osg::Camera*, TestResult > ResultMap;
162
mutable ResultMap _results;
163
mutable OpenThreads::Mutex _mapMutex;
165
// Needed for debug only
166
std::string _oqnName;
169
struct RetrieveQueriesCallback : public osg::Camera::DrawCallback
171
typedef std::vector<TestResult*> ResultsVector;
172
ResultsVector _results;
174
RetrieveQueriesCallback( osg::Drawable::Extensions* ext=NULL )
175
: _extensionsFallback( ext )
179
RetrieveQueriesCallback( const RetrieveQueriesCallback&, const osg::CopyOp& ) {}
180
META_Object( osgOQ, RetrieveQueriesCallback )
182
virtual void operator() (const osg::Camera& camera) const
184
if (_results.empty())
187
const osg::Timer& timer = *osg::Timer::instance();
188
osg::Timer_t start_tick = timer.tick();
189
double elapsedTime( 0. );
192
osg::Drawable::Extensions* ext;
193
if (camera.getGraphicsContext())
195
// The typical path, for osgViewer-based applications or any
196
// app that has set up a valid GraphicsCOntext for the Camera.
197
unsigned int contextID = camera.getGraphicsContext()->getState()->getContextID();
198
RetrieveQueriesCallback* const_this = const_cast<RetrieveQueriesCallback*>( this );
199
ext = const_this->getExtensions( contextID, true );
203
// No valid GraphicsContext in the Camera. This might happen in
204
// SceneView-based apps. Rely on the creating code to have passed
205
// in a valid Extensions pointer, and hope it's valid for any
206
// context that might be current.
207
osg::notify( osg::DEBUG_INFO ) << "osgOQ: RQCB: Using fallback path to obtain Extensions pointer." << std::endl;
208
ext = _extensionsFallback;
211
osg::notify( osg::FATAL ) << "osgOQ: RQCB: Extensions pointer fallback is NULL." << std::endl;
216
ResultsVector::const_iterator it = _results.begin();
217
while (it != _results.end())
219
TestResult* tr = const_cast<TestResult*>( *it );
221
if (!tr->_active || !tr->_init)
223
// This test wasn't executed last frame. This is probably because
224
// a parent node failed the OQ test, this node is outside the
225
// view volume, or we didn't run the test because we had not
226
// exceeded visibleQueryFrameCount.
227
// Do not obtain results from OpenGL.
232
osg::notify( osg::DEBUG_INFO ) <<
233
"osgOQ: RQCB: Retrieving..." << std::endl;
235
ext->glGetQueryObjectiv( tr->_id, GL_QUERY_RESULT, &(tr->_numPixels) );
236
if (tr->_numPixels < 0)
237
osg::notify( osg::WARN ) << "osgOQ: RQCB: " <<
238
"glGetQueryObjectiv returned negative value (" << tr->_numPixels << ")." << std::endl;
240
// Either retrieve last frame's results, or ignore it because the
241
// camera is inside the view. In either case, _active is now false.
248
elapsedTime = timer.delta_s(start_tick,timer.tick());
249
osg::notify( osg::INFO ) << "osgOQ: RQCB: " << "Retrieved " << count <<
250
" queries in " << elapsedTime << " seconds." << std::endl;
258
void add( TestResult* tr )
260
_results.push_back( tr );
263
osg::Drawable::Extensions* getExtensions( unsigned int contextID, bool createIfNotInitalized )
265
if (!s_OQ_bufferedExtensions[ contextID ] && createIfNotInitalized)
266
s_OQ_bufferedExtensions[ contextID ] = new osg::Drawable::Extensions( contextID );
267
return s_OQ_bufferedExtensions[ contextID ].get();
271
osg::Drawable::Extensions* _extensionsFallback;
276
// PreDraw callback; clears the list of Results from the PostDrawCallback (above).
277
struct ClearQueriesCallback : public osg::Camera::DrawCallback
279
ClearQueriesCallback() : _rqcb( NULL ) {}
280
ClearQueriesCallback( const ClearQueriesCallback&, const osg::CopyOp& ) {}
281
META_Object( osgOQ, ClearQueriesCallback )
283
virtual void operator() (const osg::Camera& camera) const
287
osg::notify( osg::FATAL ) << "oagOQ: CQCB: Invalid RQCB." << std::endl;
293
RetrieveQueriesCallback* _rqcb;
297
// static cache of deleted query objects which can only
298
// be completely deleted once the appropriate OpenGL context
300
typedef std::list< GLuint > QueryObjectList;
301
typedef osg::buffered_object< QueryObjectList > DeletedQueryObjectCache;
303
static OpenThreads::Mutex s_mutex_deletedQueryObjectCache;
304
static DeletedQueryObjectCache s_deletedQueryObjectCache;
306
QueryGeometry::QueryGeometry( const std::string& oqnName )
307
: _oqnName( oqnName )
309
// TBD check to see if we can have this on.
310
setUseDisplayList( false );
313
QueryGeometry::~QueryGeometry()
320
QueryGeometry::reset()
322
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _mapMutex );
324
ResultMap::iterator it = _results.begin();
325
while (it != _results.end())
327
TestResult& tr = it->second;
329
QueryGeometry::deleteQueryObject( tr._contextID, tr._id );
335
// After 1.2, param 1 changed from State to RenderInfo.
336
// Warning: Version was still 1.2 on dev branch long after the 1.2 release,
337
// and finally got bumped to 1.9 in April 2007.
339
QueryGeometry::drawImplementation( osg::RenderInfo& renderInfo ) const
341
unsigned int contextID = renderInfo.getState()->getContextID();
342
osg::Drawable::Extensions* ext = getExtensions( contextID, true );
343
osg::Camera* cam = renderInfo.getCurrentCamera();
345
// Add callbacks if necessary.
346
if (!cam->getPostDrawCallback())
348
RetrieveQueriesCallback* rqcb = new RetrieveQueriesCallback( ext );
349
cam->setPostDrawCallback( rqcb );
351
ClearQueriesCallback* cqcb = new ClearQueriesCallback;
353
cam->setPreDrawCallback( cqcb );
356
// Get TestResult from Camera map
359
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _mapMutex );
360
tr = &( _results[ cam ] );
363
// Add TestResult to RQCB.
364
RetrieveQueriesCallback* rqcb = dynamic_cast<
365
RetrieveQueriesCallback* >( cam->getPostDrawCallback() );
368
osg::notify( osg::FATAL ) << "oagOQ: QG: Invalid RQCB." << std::endl;
377
ext->glGenQueries( 1, &(tr->_id) );
378
tr->_contextID = contextID;
382
osg::notify( osg::DEBUG_INFO ) <<
383
"oagOQ: QG: Querying for: " << _oqnName << std::endl;
385
ext->glBeginQuery( GL_SAMPLES_PASSED_ARB, tr->_id );
386
Geometry::drawImplementation( renderInfo );
387
ext->glEndQuery( GL_SAMPLES_PASSED_ARB );
391
osg::notify( osg::DEBUG_INFO ) <<
392
"osgOQ: QG. OQNName: " << _oqnName <<
393
", Ctx: " << contextID <<
394
", ID: " << tr->_id << std::endl;
398
if ((err = glGetError()) != GL_NO_ERROR)
399
osg::notify( osg::FATAL ) <<
400
"osgOQ: QG: OpenGL error: " << err << "." << std::endl;
409
QueryGeometry::getNumPixels( const osg::Camera* cam )
413
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _mapMutex );
414
tr = _results[ cam ];
416
return tr._numPixels;
421
QueryGeometry::releaseGLObjects( osg::State* state )
424
// delete all query IDs for all contexts.
429
// Delete all query IDs for the specified context.
430
unsigned int contextID = state->getContextID();
431
ResultMap::iterator it = _results.begin();
432
while (it != _results.end())
434
TestResult& tr = it->second;
435
if (tr._contextID == contextID)
437
QueryGeometry::deleteQueryObject( contextID, tr._id );
446
QueryGeometry::deleteQueryObject( unsigned int contextID, GLuint handle )
450
OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_mutex_deletedQueryObjectCache );
452
// insert the handle into the cache for the appropriate context.
453
s_deletedQueryObjectCache[contextID].push_back( handle );
459
QueryGeometry::flushDeletedQueryObjects( unsigned int contextID, double /*currentTime*/, double& availableTime )
461
// if no time available don't try to flush objects.
462
if (availableTime<=0.0) return;
464
const osg::Timer& timer = *osg::Timer::instance();
465
osg::Timer_t start_tick = timer.tick();
466
double elapsedTime = 0.0;
469
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(s_mutex_deletedQueryObjectCache);
471
const osg::Drawable::Extensions* extensions = getExtensions( contextID, true );
473
QueryObjectList& qol = s_deletedQueryObjectCache[contextID];
475
for(QueryObjectList::iterator titr=qol.begin();
476
titr!=qol.end() && elapsedTime<availableTime;
479
extensions->glDeleteQueries( 1L, &(*titr ) );
480
titr = qol.erase(titr);
481
elapsedTime = timer.delta_s(start_tick,timer.tick());
485
availableTime -= elapsedTime;
489
QueryGeometry::discardDeletedQueryObjects( unsigned int contextID )
491
OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_mutex_deletedQueryObjectCache );
492
QueryObjectList& qol = s_deletedQueryObjectCache[ contextID ];
496
// End support classes
505
OcclusionQueryNode::OcclusionQueryNode()
507
_visThreshold( 500 ),
508
_queryFrameCount( 5 ),
511
setDataVariance( osg::Object::DYNAMIC );
513
// OQN has two Geode member variables, one for doing the
514
// query and one for rendering the debug geometry.
515
// Create and initialize them.
516
createSupportNodes();
519
OcclusionQueryNode::~OcclusionQueryNode()
523
OcclusionQueryNode::OcclusionQueryNode( const OcclusionQueryNode& oqn, const osg::CopyOp& copyop )
524
: Group( oqn, copyop ),
527
_enabled = oqn._enabled;
528
_visThreshold = oqn._visThreshold;
529
_queryFrameCount = oqn._queryFrameCount;
530
_debugBB = oqn._debugBB;
532
// Regardless of shallow or deep, create unique support nodes.
533
createSupportNodes();
538
OcclusionQueryNode::getPassed( const osg::Camera* camera, float distanceToEyePoint )
541
// Queries are not enabled. The caller should be osgUtil::CullVisitor,
542
// return true to traverse the subgraphs.
545
// In the future, we could hold a reference directly to the QueryDrawable
546
// to avoid the dynamic_cast.
547
QueryGeometry* qg = dynamic_cast< QueryGeometry* >( _queryGeode->getDrawable( 0 ) );
550
osg::notify( osg::FATAL ) <<
551
"osgOQ: OcclusionQueryNode: No QueryGeometry." << std::endl;
552
// Something's broke. Return true so we at least render correctly.
556
// If the distance to the bounding sphere shell is positive, retrieve
557
// the results. Others (we're inside the BS shell) we are considered
558
// to have passed and don't need to retrieve the query.
559
const osg::BoundingSphere& bs = getBound();
560
float distance = distanceToEyePoint - bs._radius;
561
_passed = ( distance <= 0.f );
564
int result = qg->getNumPixels( camera );
565
_passed = ( (unsigned int)(result) > _visThreshold );
572
OcclusionQueryNode::traverseQuery( const osg::Camera* camera, osg::NodeVisitor& nv )
576
const int curFrame = nv.getTraversalNumber();
578
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _frameCountMutex );
579
int& lastQueryFrame = _frameCountMap[ camera ];
580
if ( issueQuery = (curFrame - lastQueryFrame >= _queryFrameCount) )
581
lastQueryFrame = curFrame;
584
_queryGeode->accept( nv );
588
OcclusionQueryNode::traverseDebug( osg::NodeVisitor& nv )
591
// If requested, display the debug geometry
592
_debugGeode->accept( nv );
596
OcclusionQueryNode::computeBound() const
599
// Need to make this routine thread-safe. Typically called by the update
600
// Visitor, or just after the update traversal, but could be called by
601
// an application thread or by a non-osgViewer application.
602
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _computeBoundMutex ) ;
604
// This is the logical place to put this code, but the method is const. Cast
605
// away constness to compute the bounding box and modify the query geometry.
606
osg::OcclusionQueryNode* nonConstThis = const_cast<osg::OcclusionQueryNode*>( this );
609
osg::ComputeBoundsVisitor cbv;
610
nonConstThis->accept( cbv );
611
osg::BoundingBox bb = cbv.getBoundingBox();
613
osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
615
(*v)[0] = osg::Vec3( bb._min.x(), bb._min.y(), bb._min.z() );
616
(*v)[1] = osg::Vec3( bb._max.x(), bb._min.y(), bb._min.z() );
617
(*v)[2] = osg::Vec3( bb._max.x(), bb._min.y(), bb._max.z() );
618
(*v)[3] = osg::Vec3( bb._min.x(), bb._min.y(), bb._max.z() );
619
(*v)[4] = osg::Vec3( bb._max.x(), bb._max.y(), bb._min.z() );
620
(*v)[5] = osg::Vec3( bb._min.x(), bb._max.y(), bb._min.z() );
621
(*v)[6] = osg::Vec3( bb._min.x(), bb._max.y(), bb._max.z() );
622
(*v)[7] = osg::Vec3( bb._max.x(), bb._max.y(), bb._max.z() );
624
osg::Geometry* geom = dynamic_cast< osg::Geometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) );
625
geom->setVertexArray( v.get() );
627
geom = dynamic_cast< osg::Geometry* >( nonConstThis->_debugGeode->getDrawable( 0 ) );
628
geom->setVertexArray( v.get() );
631
return Group::computeBound();
635
// Should only be called outside of cull/draw. No thread issues.
637
OcclusionQueryNode::setQueriesEnabled( bool enable )
642
// Should only be called outside of cull/draw. No thread issues.
644
OcclusionQueryNode::setDebugDisplay( bool debug )
649
OcclusionQueryNode::getDebugDisplay() const
657
OcclusionQueryNode::setQueryStateSet( osg::StateSet* ss )
661
osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl;
665
_queryGeode->setStateSet( ss );
668
OcclusionQueryNode::getQueryStateSet()
672
osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl;
675
return _queryGeode->getStateSet();
679
OcclusionQueryNode::getQueryStateSet() const
683
osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl;
686
return _queryGeode->getStateSet();
690
OcclusionQueryNode::setDebugStateSet( osg::StateSet* ss )
694
osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl;
697
_debugGeode->setStateSet( ss );
701
OcclusionQueryNode::getDebugStateSet()
703
if (!_debugGeode.valid())
705
osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl;
708
return _debugGeode->getStateSet();
711
OcclusionQueryNode::getDebugStateSet() const
713
if (!_debugGeode.valid())
715
osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl;
718
return _debugGeode->getStateSet();
722
OcclusionQueryNode::getPassed() const
729
OcclusionQueryNode::createSupportNodes()
731
GLushort indices[] = { 0, 1, 2, 3, 4, 5, 6, 7,
732
0, 3, 6, 5, 2, 1, 4, 7,
733
5, 4, 1, 0, 2, 7, 6, 3 };
736
// Add the test geometry Geode
737
_queryGeode = new osg::Geode;
738
_queryGeode->setName( "OQTest" );
739
_queryGeode->setDataVariance( osg::Object::DYNAMIC );
741
osg::ref_ptr< QueryGeometry > geom = new QueryGeometry( getName() );
742
geom->setDataVariance( osg::Object::DYNAMIC );
743
geom->addPrimitiveSet( new osg::DrawElementsUShort(
744
osg::PrimitiveSet::QUADS, 24, indices ) );
746
_queryGeode->addDrawable( geom.get() );
750
// Add a Geode that is a visual representation of the
751
// test geometry for debugging purposes
752
_debugGeode = new osg::Geode;
753
_debugGeode->setName( "Debug" );
754
_debugGeode->setDataVariance( osg::Object::DYNAMIC );
756
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
757
geom->setDataVariance( osg::Object::DYNAMIC );
759
osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array;
760
ca->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) );
761
geom->setColorArray( ca.get() );
762
geom->setColorBinding( osg::Geometry::BIND_OVERALL );
764
geom->addPrimitiveSet( new osg::DrawElementsUShort(
765
osg::PrimitiveSet::QUADS, 24, indices ) );
767
_debugGeode->addDrawable( geom.get() );
770
// Creste state sets. Note that the osgOQ visitors (which place OQNs throughout
771
// the scene graph) create a single instance of these StateSets shared
772
// between all OQNs for efficiency.
773
setQueryStateSet( initOQState() );
774
setDebugStateSet( initOQDebugState() );
779
OcclusionQueryNode::releaseGLObjects( osg::State* state ) const
781
// Query object discard and deletion is handled by QueryGeometry support class.
782
OcclusionQueryNode* nonConstThis = const_cast< OcclusionQueryNode* >( this );
783
QueryGeometry* qg = dynamic_cast< QueryGeometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) );
784
qg->releaseGLObjects( state );
788
OcclusionQueryNode::flushDeletedQueryObjects( unsigned int contextID, double currentTime, double& availableTime )
790
// Query object discard and deletion is handled by QueryGeometry support class.
791
QueryGeometry::flushDeletedQueryObjects( contextID, currentTime, availableTime );
795
OcclusionQueryNode::discardDeletedQueryObjects( unsigned int contextID )
797
// Query object discard and deletion is handled by QueryGeometry support class.
798
QueryGeometry::discardDeletedQueryObjects( contextID );