diff --git a/include/osg/OcclusionQueryNode b/include/osg/OcclusionQueryNode index b0f47bb9f..6e2dc7127 100644 --- a/include/osg/OcclusionQueryNode +++ b/include/osg/OcclusionQueryNode @@ -72,7 +72,7 @@ public: virtual void drawImplementation( osg::RenderInfo& renderInfo ) const; - unsigned int getNumPixels( const osg::Camera* cam ); + unsigned int getNumPixels(); void releaseGLObjects( osg::State* state = 0 ); @@ -81,9 +81,8 @@ public: static void discardDeletedQueryObjects( unsigned int contextID ); protected: - typedef std::map< const osg::Camera*, TestResult > ResultMap; + typedef osg::buffered_object< TestResult > ResultMap; mutable ResultMap _results; - mutable OpenThreads::Mutex _mapMutex; // Needed for debug only std::string _oqnName; diff --git a/src/osg/OcclusionQueryNode.cpp b/src/osg/OcclusionQueryNode.cpp index a9dba8f06..a85db7fae 100644 --- a/src/osg/OcclusionQueryNode.cpp +++ b/src/osg/OcclusionQueryNode.cpp @@ -1,789 +1,809 @@ -// -// Copyright (C) 2007 Skew Matrix Software LLC (http://www.skew-matrix.com) -// -// This library is open source and may be redistributed and/or modified under -// the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or -// (at your option) any later version. The full license is in LICENSE file -// included with this distribution, and on the openscenegraph.org website. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// OpenSceneGraph Public License for more details. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - - -typedef osg::buffered_value< osg::ref_ptr< osg::Drawable::Extensions > > OcclusionQueryBufferedExtensions; -static OcclusionQueryBufferedExtensions s_OQ_bufferedExtensions; - -// -// Support classes, used by (and private to) OcclusionQueryNode. -// (Note a lot of this is historical. OcclusionQueryNode formaerly -// existed as a NodeKit outside the core OSG distribution. Many -// of these classes existed in their own separate header and -// source files.) - -namespace osg -{ - -// Create and return a StateSet appropriate for performing an occlusion -// query test (disable lighting, texture mapping, etc). Probably some -// room for improvement here. Could disable shaders, for example. -osg::StateSet* -initOQState() -{ - osg::StateSet* state = new osg::StateSet; - // TBD Possible bug, need to allow user to set render bin number. - state->setRenderBinDetails( 9, "RenderBin" ); - - state->setMode( GL_LIGHTING, osg::StateAttribute::OFF | - osg::StateAttribute::PROTECTED); - state->setTextureMode( 0, GL_TEXTURE_2D, osg::StateAttribute::OFF | - osg::StateAttribute::PROTECTED); - state->setMode( GL_CULL_FACE, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - - osg::ColorMask* cm = new osg::ColorMask( false, false, false, false ); - state->setAttributeAndModes( cm, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - osg::Depth* d = new osg::Depth( osg::Depth::LEQUAL, 0.f, 1.f, false ); - state->setAttributeAndModes( d, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - osg::PolygonMode* pm = new osg::PolygonMode( - osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL ); - state->setAttributeAndModes( pm, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - - osg::PolygonOffset* po = new osg::PolygonOffset( -1., -1. ); - state->setAttributeAndModes( po, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - - return state; -} - -// Create and return a StateSet for rendering a debug representation of query geometry. -osg::StateSet* -initOQDebugState() -{ - osg::StateSet* debugState = new osg::StateSet; - - debugState->setMode( GL_LIGHTING, osg::StateAttribute::OFF | - osg::StateAttribute::PROTECTED); - debugState->setTextureMode( 0, GL_TEXTURE_2D, osg::StateAttribute::OFF | - osg::StateAttribute::PROTECTED); - debugState->setMode( GL_CULL_FACE, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - - osg::PolygonMode* pm = new osg::PolygonMode( - osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE ); - debugState->setAttributeAndModes( pm, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - - osg::PolygonOffset* po = new osg::PolygonOffset( -1., -1. ); - debugState->setAttributeAndModes( po, osg::StateAttribute::ON | - osg::StateAttribute::PROTECTED); - - return debugState; -} - -} - -struct RetrieveQueriesCallback : public osg::Camera::DrawCallback -{ - typedef std::vector ResultsVector; - ResultsVector _results; - - RetrieveQueriesCallback( osg::Drawable::Extensions* ext=NULL ) - : _extensionsFallback( ext ) - { - } - - RetrieveQueriesCallback( const RetrieveQueriesCallback&, const osg::CopyOp& ) {} - META_Object( osgOQ, RetrieveQueriesCallback ) - - virtual void operator() (const osg::Camera& camera) const - { - if (_results.empty()) - return; - - const osg::Timer& timer = *osg::Timer::instance(); - osg::Timer_t start_tick = timer.tick(); - double elapsedTime( 0. ); - int count( 0 ); - - osg::Drawable::Extensions* ext; - if (camera.getGraphicsContext()) - { - // The typical path, for osgViewer-based applications or any - // app that has set up a valid GraphicsCOntext for the Camera. - unsigned int contextID = camera.getGraphicsContext()->getState()->getContextID(); - RetrieveQueriesCallback* const_this = const_cast( this ); - ext = const_this->getExtensions( contextID, true ); - } - else - { - // No valid GraphicsContext in the Camera. This might happen in - // SceneView-based apps. Rely on the creating code to have passed - // in a valid Extensions pointer, and hope it's valid for any - // context that might be current. - osg::notify( osg::DEBUG_INFO ) << "osgOQ: RQCB: Using fallback path to obtain Extensions pointer." << std::endl; - ext = _extensionsFallback; - if (!ext) - { - osg::notify( osg::FATAL ) << "osgOQ: RQCB: Extensions pointer fallback is NULL." << std::endl; - return; - } - } - - ResultsVector::const_iterator it = _results.begin(); - while (it != _results.end()) - { - osg::TestResult* tr = const_cast( *it ); - - if (!tr->_active || !tr->_init) - { - // This test wasn't executed last frame. This is probably because - // a parent node failed the OQ test, this node is outside the - // view volume, or we didn't run the test because we had not - // exceeded visibleQueryFrameCount. - // Do not obtain results from OpenGL. - it++; - continue; - } - - osg::notify( osg::DEBUG_INFO ) << - "osgOQ: RQCB: Retrieving..." << std::endl; - -#ifdef FORCE_QUERY_RESULT_AVAILABLE_BEFORE_RETRIEVAL - - // Should not need to do this, but is required on some platforms to - // work aroung issues in the device driver. For example, without this - // code, we've seen crashes on 64-bit Mac/Linux NVIDIA systems doing - // multithreaded, multipipe rendering (as in a CAVE). - // Tried with ATI and verified this workaround is not needed; the - // problem is specific to NVIDIA. - GLint ready( 0 ); - while( !ready ) - { - // Apparently, must actually sleep here to avoid issues w/ NVIDIA Quadro. - OpenThreads::Thread::microSleep( 5 ); - ext->glGetQueryObjectiv( tr->_id, GL_QUERY_RESULT_AVAILABLE, &ready ); - }; -#endif - - ext->glGetQueryObjectiv( tr->_id, GL_QUERY_RESULT, &(tr->_numPixels) ); - if (tr->_numPixels < 0) - osg::notify( osg::WARN ) << "osgOQ: RQCB: " << - "glGetQueryObjectiv returned negative value (" << tr->_numPixels << ")." << std::endl; - - // Either retrieve last frame's results, or ignore it because the - // camera is inside the view. In either case, _active is now false. - tr->_active = false; - - it++; - count++; - } - - elapsedTime = timer.delta_s(start_tick,timer.tick()); - osg::notify( osg::INFO ) << "osgOQ: RQCB: " << "Retrieved " << count << - " queries in " << elapsedTime << " seconds." << std::endl; - } - - void reset() - { - _results.clear(); - } - - void add( osg::TestResult* tr ) - { - _results.push_back( tr ); - } - - osg::Drawable::Extensions* getExtensions( unsigned int contextID, bool createIfNotInitalized ) - { - if (!s_OQ_bufferedExtensions[ contextID ] && createIfNotInitalized) - s_OQ_bufferedExtensions[ contextID ] = new osg::Drawable::Extensions( contextID ); - return s_OQ_bufferedExtensions[ contextID ].get(); - } - - - osg::Drawable::Extensions* _extensionsFallback; -}; - - - -// PreDraw callback; clears the list of Results from the PostDrawCallback (above). -struct ClearQueriesCallback : public osg::Camera::DrawCallback -{ - ClearQueriesCallback() : _rqcb( NULL ) {} - ClearQueriesCallback( const ClearQueriesCallback&, const osg::CopyOp& ) {} - META_Object( osgOQ, ClearQueriesCallback ) - - virtual void operator() (const osg::Camera&) const - { - if (!_rqcb) - { - osg::notify( osg::FATAL ) << "osgOQ: CQCB: Invalid RQCB." << std::endl; - return; - } - _rqcb->reset(); - } - - RetrieveQueriesCallback* _rqcb; -}; - - -// static cache of deleted query objects which can only -// be completely deleted once the appropriate OpenGL context -// is set. -typedef std::list< GLuint > QueryObjectList; -typedef osg::buffered_object< QueryObjectList > DeletedQueryObjectCache; - -static OpenThreads::Mutex s_mutex_deletedQueryObjectCache; -static DeletedQueryObjectCache s_deletedQueryObjectCache; - -namespace osg -{ - - -QueryGeometry::QueryGeometry( const std::string& oqnName ) - : _oqnName( oqnName ) -{ - // TBD check to see if we can have this on. - setUseDisplayList( false ); -} - -QueryGeometry::~QueryGeometry() -{ - reset(); -} - - -void -QueryGeometry::reset() -{ - OpenThreads::ScopedLock lock( _mapMutex ); - - ResultMap::iterator it = _results.begin(); - while (it != _results.end()) - { - TestResult& tr = it->second; - if (tr._init) - QueryGeometry::deleteQueryObject( tr._contextID, tr._id ); - it++; - } - _results.clear(); -} - -// After 1.2, param 1 changed from State to RenderInfo. -// Warning: Version was still 1.2 on dev branch long after the 1.2 release, -// and finally got bumped to 1.9 in April 2007. -void -QueryGeometry::drawImplementation( osg::RenderInfo& renderInfo ) const -{ - unsigned int contextID = renderInfo.getState()->getContextID(); - osg::Drawable::Extensions* ext = getExtensions( contextID, true ); - osg::Camera* cam = renderInfo.getCurrentCamera(); - - // Add callbacks if necessary. - if (!cam->getPostDrawCallback()) - { - RetrieveQueriesCallback* rqcb = new RetrieveQueriesCallback( ext ); - cam->setPostDrawCallback( rqcb ); - - ClearQueriesCallback* cqcb = new ClearQueriesCallback; - cqcb->_rqcb = rqcb; - cam->setPreDrawCallback( cqcb ); - } - - // Get TestResult from Camera map - TestResult* tr; - { - OpenThreads::ScopedLock lock( _mapMutex ); - tr = &( _results[ cam ] ); - } - - // Add TestResult to RQCB. - RetrieveQueriesCallback* rqcb = dynamic_cast< - RetrieveQueriesCallback* >( cam->getPostDrawCallback() ); - if (!rqcb) - { - osg::notify( osg::FATAL ) << "osgOQ: QG: Invalid RQCB." << std::endl; - return; - } - rqcb->add( tr ); - - - // Issue query - if (!tr->_init) - { - ext->glGenQueries( 1, &(tr->_id) ); - tr->_contextID = contextID; - tr->_init = true; - } - - osg::notify( osg::DEBUG_INFO ) << - "osgOQ: QG: Querying for: " << _oqnName << std::endl; - - ext->glBeginQuery( GL_SAMPLES_PASSED_ARB, tr->_id ); - osg::Geometry::drawImplementation( renderInfo ); - ext->glEndQuery( GL_SAMPLES_PASSED_ARB ); - tr->_active = true; - - - osg::notify( osg::DEBUG_INFO ) << - "osgOQ: QG. OQNName: " << _oqnName << - ", Ctx: " << contextID << - ", ID: " << tr->_id << std::endl; -#ifdef _DEBUG - { - GLenum err; - if ((err = glGetError()) != GL_NO_ERROR) - osg::notify( osg::FATAL ) << - "osgOQ: QG: OpenGL error: " << err << "." << std::endl; - } -#endif - - -} - - -unsigned int -QueryGeometry::getNumPixels( const osg::Camera* cam ) -{ - TestResult tr; - { - OpenThreads::ScopedLock lock( _mapMutex ); - tr = _results[ cam ]; - } - return tr._numPixels; -} - - -void -QueryGeometry::releaseGLObjects( osg::State* state ) -{ - if (!state) - // delete all query IDs for all contexts. - reset(); - - else - { - // Delete all query IDs for the specified context. - unsigned int contextID = state->getContextID(); - ResultMap::iterator it = _results.begin(); - while (it != _results.end()) - { - TestResult& tr = it->second; - if (tr._contextID == contextID) - { - QueryGeometry::deleteQueryObject( contextID, tr._id ); - tr._init = false; - } - it++; - } - } -} - -void -QueryGeometry::deleteQueryObject( unsigned int contextID, GLuint handle ) -{ - if (handle!=0) - { - OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_mutex_deletedQueryObjectCache ); - - // insert the handle into the cache for the appropriate context. - s_deletedQueryObjectCache[contextID].push_back( handle ); - } -} - - -void -QueryGeometry::flushDeletedQueryObjects( unsigned int contextID, double /*currentTime*/, double& availableTime ) -{ - // if no time available don't try to flush objects. - if (availableTime<=0.0) return; - - const osg::Timer& timer = *osg::Timer::instance(); - osg::Timer_t start_tick = timer.tick(); - double elapsedTime = 0.0; - - { - OpenThreads::ScopedLock lock(s_mutex_deletedQueryObjectCache); - - const osg::Drawable::Extensions* extensions = getExtensions( contextID, true ); - - QueryObjectList& qol = s_deletedQueryObjectCache[contextID]; - - for(QueryObjectList::iterator titr=qol.begin(); - titr!=qol.end() && elapsedTimeglDeleteQueries( 1L, &(*titr ) ); - titr = qol.erase(titr); - elapsedTime = timer.delta_s(start_tick,timer.tick()); - } - } - - availableTime -= elapsedTime; -} - -void -QueryGeometry::discardDeletedQueryObjects( unsigned int contextID ) -{ - OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_mutex_deletedQueryObjectCache ); - QueryObjectList& qol = s_deletedQueryObjectCache[ contextID ]; - qol.clear(); -} - -// End support classes -// - - - - -OcclusionQueryNode::OcclusionQueryNode() - : _enabled( true ), - _visThreshold( 500 ), - _queryFrameCount( 5 ), - _debugBB( false ) -{ - // OQN has two Geode member variables, one for doing the - // query and one for rendering the debug geometry. - // Create and initialize them. - createSupportNodes(); -} - -OcclusionQueryNode::~OcclusionQueryNode() -{ -} - -OcclusionQueryNode::OcclusionQueryNode( const OcclusionQueryNode& oqn, const osg::CopyOp& copyop ) - : Group( oqn, copyop ), - _passed( false ) -{ - _enabled = oqn._enabled; - _visThreshold = oqn._visThreshold; - _queryFrameCount = oqn._queryFrameCount; - _debugBB = oqn._debugBB; - - // Regardless of shallow or deep, create unique support nodes. - createSupportNodes(); -} - - -bool -OcclusionQueryNode::getPassed( const osg::Camera* camera, osg::NodeVisitor& nv ) -{ - if ( !_enabled ) - // Queries are not enabled. The caller should be osgUtil::CullVisitor, - // return true to traverse the subgraphs. - return true; - - { - // Two situations where we want to simply do a regular traversal: - // 1) it's the first frame for this camers - // 2) we haven't rendered for an abnormally long time (probably because we're an out-of-range LOD child) - // In these cases, assume we're visible to avoid blinking. - OpenThreads::ScopedLock lock( _frameCountMutex ); - const int& lastQueryFrame( _frameCountMap[ camera ] ); - if( ( lastQueryFrame == 0 ) || - ( (nv.getTraversalNumber() - lastQueryFrame) > (_queryFrameCount + 1) ) ) - return true; - } - - if (_queryGeode->getDrawable( 0 ) == NULL) - { - osg::notify( osg::FATAL ) << - "osgOQ: OcclusionQueryNode: No QueryGeometry." << std::endl; - // Something's broke. Return true so we at least render correctly. - return true; - } - QueryGeometry* qg = static_cast< QueryGeometry* >( _queryGeode->getDrawable( 0 ) ); - - // Get the near plane for the upcoming distance calculation. - float nearPlane; - const osg::Matrix& proj( camera->getProjectionMatrix() ); - if( ( proj(3,3) != 1. ) || ( proj(2,3) != 0. ) || ( proj(1,3) != 0. ) || ( proj(0,3) != 0.) ) - nearPlane = proj(3,2) / (proj(2,2)-1.); // frustum / perspective - else - nearPlane = (proj(3,2)+1.) / proj(2,2); // ortho - - // If the distance from the near plane to the bounding sphere shell is positive, retrieve - // the results. Otherwise (near plane inside the BS shell) we are considered - // to have passed and don't need to retrieve the query. - const osg::BoundingSphere& bs = getBound(); - float distanceToEyePoint = nv.getDistanceToEyePoint( bs._center, false ); - - float distance = distanceToEyePoint - nearPlane - bs._radius; - _passed = ( distance <= 0.f ); - if (!_passed) - { - int result = qg->getNumPixels( camera ); - _passed = ( (unsigned int)(result) > _visThreshold ); - } - - return _passed; -} - -void -OcclusionQueryNode::traverseQuery( const osg::Camera* camera, osg::NodeVisitor& nv ) -{ - bool issueQuery; - { - const int curFrame = nv.getTraversalNumber(); - - OpenThreads::ScopedLock lock( _frameCountMutex ); - int& lastQueryFrame = _frameCountMap[ camera ]; - issueQuery = (curFrame - lastQueryFrame >= _queryFrameCount); - if (issueQuery) - lastQueryFrame = curFrame; - } - if (issueQuery) - _queryGeode->accept( nv ); -} - -void -OcclusionQueryNode::traverseDebug( osg::NodeVisitor& nv ) -{ - if (_debugBB) - // If requested, display the debug geometry - _debugGeode->accept( nv ); -} - -osg::BoundingSphere -OcclusionQueryNode::computeBound() const -{ - { - // Need to make this routine thread-safe. Typically called by the update - // Visitor, or just after the update traversal, but could be called by - // an application thread or by a non-osgViewer application. - OpenThreads::ScopedLock lock( _computeBoundMutex ) ; - - // This is the logical place to put this code, but the method is const. Cast - // away constness to compute the bounding box and modify the query geometry. - osg::OcclusionQueryNode* nonConstThis = const_cast( this ); - - - osg::ComputeBoundsVisitor cbv; - nonConstThis->accept( cbv ); - osg::BoundingBox bb = cbv.getBoundingBox(); - - osg::ref_ptr v = new osg::Vec3Array; - v->resize( 8 ); - (*v)[0] = osg::Vec3( bb._min.x(), bb._min.y(), bb._min.z() ); - (*v)[1] = osg::Vec3( bb._max.x(), bb._min.y(), bb._min.z() ); - (*v)[2] = osg::Vec3( bb._max.x(), bb._min.y(), bb._max.z() ); - (*v)[3] = osg::Vec3( bb._min.x(), bb._min.y(), bb._max.z() ); - (*v)[4] = osg::Vec3( bb._max.x(), bb._max.y(), bb._min.z() ); - (*v)[5] = osg::Vec3( bb._min.x(), bb._max.y(), bb._min.z() ); - (*v)[6] = osg::Vec3( bb._min.x(), bb._max.y(), bb._max.z() ); - (*v)[7] = osg::Vec3( bb._max.x(), bb._max.y(), bb._max.z() ); - - osg::Geometry* geom = static_cast< osg::Geometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) ); - geom->setVertexArray( v.get() ); - - geom = static_cast< osg::Geometry* >( nonConstThis->_debugGeode->getDrawable( 0 ) ); - geom->setVertexArray( v.get() ); - } - - return Group::computeBound(); -} - - -// Should only be called outside of cull/draw. No thread issues. -void -OcclusionQueryNode::setQueriesEnabled( bool enable ) -{ - _enabled = enable; -} - -// Should only be called outside of cull/draw. No thread issues. -void -OcclusionQueryNode::setDebugDisplay( bool debug ) -{ - _debugBB = debug; -} -bool -OcclusionQueryNode::getDebugDisplay() const -{ - return _debugBB; -} - - - -void -OcclusionQueryNode::setQueryStateSet( osg::StateSet* ss ) -{ - if (!_queryGeode) - { - osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl; - return; - } - - _queryGeode->setStateSet( ss ); -} -osg::StateSet* -OcclusionQueryNode::getQueryStateSet() -{ - if (!_queryGeode) - { - osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl; - return NULL; - } - return _queryGeode->getStateSet(); -} - -const osg::StateSet* -OcclusionQueryNode::getQueryStateSet() const -{ - if (!_queryGeode) - { - osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl; - return NULL; - } - return _queryGeode->getStateSet(); -} - -void -OcclusionQueryNode::setDebugStateSet( osg::StateSet* ss ) -{ - if (!_debugGeode) - { - osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl; - return; - } - _debugGeode->setStateSet( ss ); -} - -osg::StateSet* -OcclusionQueryNode::getDebugStateSet() -{ - if (!_debugGeode.valid()) - { - osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl; - return NULL; - } - return _debugGeode->getStateSet(); -} -const osg::StateSet* -OcclusionQueryNode::getDebugStateSet() const -{ - if (!_debugGeode.valid()) - { - osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl; - return NULL; - } - return _debugGeode->getStateSet(); -} - -bool -OcclusionQueryNode::getPassed() const -{ - return _passed; -} - - -void -OcclusionQueryNode::createSupportNodes() -{ - GLushort indices[] = { 0, 1, 2, 3, 4, 5, 6, 7, - 0, 3, 6, 5, 2, 1, 4, 7, - 5, 4, 1, 0, 2, 7, 6, 3 }; - - { - // Add the test geometry Geode - _queryGeode = new osg::Geode; - _queryGeode->setName( "OQTest" ); - _queryGeode->setDataVariance( osg::Object::DYNAMIC ); - - osg::ref_ptr< QueryGeometry > geom = new QueryGeometry( getName() ); - geom->setDataVariance( osg::Object::DYNAMIC ); - geom->addPrimitiveSet( new osg::DrawElementsUShort( - osg::PrimitiveSet::QUADS, 24, indices ) ); - - _queryGeode->addDrawable( geom.get() ); - } - - { - // Add a Geode that is a visual representation of the - // test geometry for debugging purposes - _debugGeode = new osg::Geode; - _debugGeode->setName( "Debug" ); - _debugGeode->setDataVariance( osg::Object::DYNAMIC ); - - osg::ref_ptr geom = new osg::Geometry; - geom->setDataVariance( osg::Object::DYNAMIC ); - - osg::ref_ptr ca = new osg::Vec4Array; - ca->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) ); - geom->setColorArray( ca.get() ); - geom->setColorBinding( osg::Geometry::BIND_OVERALL ); - - geom->addPrimitiveSet( new osg::DrawElementsUShort( - osg::PrimitiveSet::QUADS, 24, indices ) ); - - _debugGeode->addDrawable( geom.get() ); - } - - // Creste state sets. Note that the osgOQ visitors (which place OQNs throughout - // the scene graph) create a single instance of these StateSets shared - // between all OQNs for efficiency. - setQueryStateSet( initOQState() ); - setDebugStateSet( initOQDebugState() ); -} - - -void -OcclusionQueryNode::releaseGLObjects( osg::State* state ) const -{ - if(_queryGeode->getDrawable( 0 ) != NULL) - { - // Query object discard and deletion is handled by QueryGeometry support class. - OcclusionQueryNode* nonConstThis = const_cast< OcclusionQueryNode* >( this ); - QueryGeometry* qg = static_cast< QueryGeometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) ); - qg->releaseGLObjects( state ); - } -} - -void -OcclusionQueryNode::flushDeletedQueryObjects( unsigned int contextID, double currentTime, double& availableTime ) -{ - // Query object discard and deletion is handled by QueryGeometry support class. - QueryGeometry::flushDeletedQueryObjects( contextID, currentTime, availableTime ); -} - -void -OcclusionQueryNode::discardDeletedQueryObjects( unsigned int contextID ) -{ - // Query object discard and deletion is handled by QueryGeometry support class. - QueryGeometry::discardDeletedQueryObjects( contextID ); -} - - -} +// +// Copyright (C) 2007 Skew Matrix Software LLC (http://www.skew-matrix.com) +// +// This library is open source and may be redistributed and/or modified under +// the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or +// (at your option) any later version. The full license is in LICENSE file +// included with this distribution, and on the openscenegraph.org website. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// OpenSceneGraph Public License for more details. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +typedef osg::buffered_value< osg::ref_ptr< osg::Drawable::Extensions > > OcclusionQueryBufferedExtensions; +static OcclusionQueryBufferedExtensions s_OQ_bufferedExtensions; + +// +// Support classes, used by (and private to) OcclusionQueryNode. +// (Note a lot of this is historical. OcclusionQueryNode formaerly +// existed as a NodeKit outside the core OSG distribution. Many +// of these classes existed in their own separate header and +// source files.) + +namespace osg +{ + +// Create and return a StateSet appropriate for performing an occlusion +// query test (disable lighting, texture mapping, etc). Probably some +// room for improvement here. Could disable shaders, for example. +osg::StateSet* +initOQState() +{ + osg::StateSet* state = new osg::StateSet; + // TBD Possible bug, need to allow user to set render bin number. + state->setRenderBinDetails( 9, "RenderBin" ); + + state->setMode( GL_LIGHTING, osg::StateAttribute::OFF | + osg::StateAttribute::PROTECTED); + state->setTextureMode( 0, GL_TEXTURE_2D, osg::StateAttribute::OFF | + osg::StateAttribute::PROTECTED); + state->setMode( GL_CULL_FACE, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + + osg::ColorMask* cm = new osg::ColorMask( false, false, false, false ); + state->setAttributeAndModes( cm, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + osg::Depth* d = new osg::Depth( osg::Depth::LEQUAL, 0.f, 1.f, false ); + state->setAttributeAndModes( d, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + osg::PolygonMode* pm = new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL ); + state->setAttributeAndModes( pm, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + + osg::PolygonOffset* po = new osg::PolygonOffset( -1., -1. ); + state->setAttributeAndModes( po, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + + return state; +} + +// Create and return a StateSet for rendering a debug representation of query geometry. +osg::StateSet* +initOQDebugState() +{ + osg::StateSet* debugState = new osg::StateSet; + + debugState->setMode( GL_LIGHTING, osg::StateAttribute::OFF | + osg::StateAttribute::PROTECTED); + debugState->setTextureMode( 0, GL_TEXTURE_2D, osg::StateAttribute::OFF | + osg::StateAttribute::PROTECTED); + debugState->setMode( GL_CULL_FACE, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + + osg::PolygonMode* pm = new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE ); + debugState->setAttributeAndModes( pm, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + + osg::PolygonOffset* po = new osg::PolygonOffset( -1., -1. ); + debugState->setAttributeAndModes( po, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + + return debugState; +} + +} + +struct RetrieveQueriesCallback : public osg::Camera::DrawCallback +{ + typedef std::vector ResultsVector; + typedef osg::buffered_object< ResultsVector > AllResultsMap; + AllResultsMap _results; + + RetrieveQueriesCallback( osg::Drawable::Extensions* ext=NULL ) + : _extensionsFallback( ext ) + { + } + + RetrieveQueriesCallback( const RetrieveQueriesCallback&, const osg::CopyOp& ) {} + META_Object( osgOQ, RetrieveQueriesCallback ) + + virtual void operator()( osg::RenderInfo& renderInfo ) const + { + if (_results.empty()) + return; + + osg::Camera* camera = renderInfo.getCurrentCamera(); + unsigned int contextID = renderInfo.getContextID(); + const ResultsVector& rv = _results[ contextID ]; + if( rv.empty() ) + return; + + osg::notify( osg::INFO ) << "osgOQ: RQCB start, camera: " + << std::hex << camera << std::dec + << ", ctx: " << contextID << std::dec << std::endl; + + const osg::Timer& timer = *osg::Timer::instance(); + osg::Timer_t start_tick = timer.tick(); + double elapsedTime( 0. ); + int count( 0 ); + + osg::Drawable::Extensions* ext; + if (camera->getGraphicsContext()) + { + // The typical path, for osgViewer-based applications or any + // app that has set up a valid GraphicsCOntext for the Camera. + unsigned int contextID = camera->getGraphicsContext()->getState()->getContextID(); + RetrieveQueriesCallback* const_this = const_cast( this ); + ext = const_this->getExtensions( contextID, true ); + } + else + { + // No valid GraphicsContext in the Camera. This might happen in + // SceneView-based apps. Rely on the creating code to have passed + // in a valid Extensions pointer, and hope it's valid for any + // context that might be current. + osg::notify( osg::DEBUG_INFO ) << "osgOQ: RQCB: Using fallback path to obtain Extensions pointer." << std::endl; + ext = _extensionsFallback; + if (!ext) + { + osg::notify( osg::FATAL ) << "osgOQ: RQCB: Extensions pointer fallback is NULL." << std::endl; + return; + } + } + + osg::notify( osg::INFO ) << "osgOQ: RQCB: rv.size() = " << rv.size() << std::endl; + ResultsVector::const_iterator it = rv.begin(); + while (it != rv.end()) + { + osg::TestResult* tr = const_cast( *it ); + //osg::notify( osg::INFO ) << "osgOQ: RQCB: TestResult = " << std::hex << tr << std::dec << std::endl; + + if (!tr->_active || !tr->_init) + { + // This test wasn't executed last frame. This is probably because + // a parent node failed the OQ test, this node is outside the + // view volume, or we didn't run the test because we had not + // exceeded visibleQueryFrameCount. + // Do not obtain results from OpenGL. + it++; + continue; + } + + osg::notify( osg::DEBUG_INFO ) << + "osgOQ: RQCB: Retrieving..." << std::endl; + +#ifdef FORCE_QUERY_RESULT_AVAILABLE_BEFORE_RETRIEVAL + + // Should not need to do this, but is required on some platforms to + // work aroung issues in the device driver. For example, without this + // code, we've seen crashes on 64-bit Mac/Linux NVIDIA systems doing + // multithreaded, multipipe rendering (as in a CAVE). + // Tried with ATI and verified this workaround is not needed; the + // problem is specific to NVIDIA. + GLint ready( 0 ); + while( !ready ) + { + // Apparently, must actually sleep here to avoid issues w/ NVIDIA Quadro. + OpenThreads::Thread::microSleep( 5 ); + ext->glGetQueryObjectiv( tr->_id, GL_QUERY_RESULT_AVAILABLE, &ready ); + }; +#endif + + ext->glGetQueryObjectiv( tr->_id, GL_QUERY_RESULT, &(tr->_numPixels) ); + if (tr->_numPixels < 0) + osg::notify( osg::WARN ) << "osgOQ: RQCB: " << + "glGetQueryObjectiv returned negative value (" << tr->_numPixels << ")." << std::endl; + + // Either retrieve last frame's results, or ignore it because the + // camera is inside the view. In either case, _active is now false. + tr->_active = false; + + it++; + count++; + } + + elapsedTime = timer.delta_s(start_tick,timer.tick()); + osg::notify( osg::INFO ) << "osgOQ: RQCB: " << "Retrieved " << count << + " queries in " << elapsedTime << " seconds." << std::endl; + } + + void reset( unsigned int contextID ) + { + // TBDrev + _results[ contextID ].clear(); + } + + void add( osg::TestResult* tr, unsigned int contextID ) + { + osg::notify( osg::INFO ) << "osgOQ: RQCB::add(), ctx: " << contextID << std::endl; + _results[ contextID ].push_back( tr ); + } + + osg::Drawable::Extensions* getExtensions( unsigned int contextID, bool createIfNotInitalized ) + { + if (!s_OQ_bufferedExtensions[ contextID ] && createIfNotInitalized) + s_OQ_bufferedExtensions[ contextID ] = new osg::Drawable::Extensions( contextID ); + return s_OQ_bufferedExtensions[ contextID ].get(); + } + + + osg::Drawable::Extensions* _extensionsFallback; +}; + + + +// PreDraw callback; clears the list of Results from the PostDrawCallback (above). +struct ClearQueriesCallback : public osg::Camera::DrawCallback +{ + ClearQueriesCallback() : _rqcb( NULL ) {} + ClearQueriesCallback( const ClearQueriesCallback&, const osg::CopyOp& ) {} + META_Object( osgOQ, ClearQueriesCallback ) + + virtual void operator()( osg::RenderInfo& renderInfo ) const + { + osg::Camera* camera = renderInfo.getCurrentCamera(); + unsigned int contextID = renderInfo.getContextID(); + osg::notify( osg::INFO ) << "osgOQ: CQCB start, camera: " + << std::hex << camera << std::dec + << ", ctx: " << renderInfo.getContextID() << std::dec << std::endl; + + if (!_rqcb) + { + osg::notify( osg::FATAL ) << "osgOQ: CQCB: Invalid RQCB." << std::endl; + return; + } + _rqcb->reset( contextID ); + + osg::notify( osg::INFO ) << "osgOQ: CQCB end." << std::endl; + } + + RetrieveQueriesCallback* _rqcb; +}; + + +// static cache of deleted query objects which can only +// be completely deleted once the appropriate OpenGL context +// is set. +typedef std::list< GLuint > QueryObjectList; +typedef osg::buffered_object< QueryObjectList > DeletedQueryObjectCache; + +static OpenThreads::Mutex s_mutex_deletedQueryObjectCache; +static DeletedQueryObjectCache s_deletedQueryObjectCache; + +namespace osg +{ + + +QueryGeometry::QueryGeometry( const std::string& oqnName ) + : _oqnName( oqnName ) +{ + // TBD check to see if we can have this on. + setUseDisplayList( false ); +} + +QueryGeometry::~QueryGeometry() +{ + reset(); +} + + +void +QueryGeometry::reset() +{ + unsigned int idx; + for( idx=0; idx<_results.size(); idx++ ) + { + TestResult& tr = _results[ idx ]; + if (tr._init) + QueryGeometry::deleteQueryObject( tr._contextID, tr._id ); + tr._init = false; + tr._active = false; + } + _results.clear(); +} + +// After 1.2, param 1 changed from State to RenderInfo. +// Warning: Version was still 1.2 on dev branch long after the 1.2 release, +// and finally got bumped to 1.9 in April 2007. +void +QueryGeometry::drawImplementation( osg::RenderInfo& renderInfo ) const +{ + unsigned int contextID = renderInfo.getState()->getContextID(); + osg::Drawable::Extensions* ext = getExtensions( contextID, true ); + osg::Camera* cam = renderInfo.getCurrentCamera(); + + // Add callbacks if necessary. + if (!cam->getPostDrawCallback()) + { + RetrieveQueriesCallback* rqcb = new RetrieveQueriesCallback( ext ); + cam->setPostDrawCallback( rqcb ); + + ClearQueriesCallback* cqcb = new ClearQueriesCallback; + cqcb->_rqcb = rqcb; + cam->setPreDrawCallback( cqcb ); + } + + TestResult* tr = &( _results[ contextID ] ); + + // Add TestResult to RQCB. + RetrieveQueriesCallback* rqcb = dynamic_cast< + RetrieveQueriesCallback* >( cam->getPostDrawCallback() ); + if (!rqcb) + { + osg::notify( osg::WARN ) << "osgOQ: QG: Invalid RQCB." << std::endl; + return; + } + rqcb->add( tr, contextID ); + + + // Issue query + if (!tr->_init) + { + ext->glGenQueries( 1, &(tr->_id) ); + tr->_contextID = contextID; + tr->_init = true; + } + + osg::notify( osg::DEBUG_INFO ) << + "osgOQ: QG: Querying for: " << _oqnName << std::endl; + + ext->glBeginQuery( GL_SAMPLES_PASSED_ARB, tr->_id ); + osg::Geometry::drawImplementation( renderInfo ); + ext->glEndQuery( GL_SAMPLES_PASSED_ARB ); + tr->_active = true; + + + osg::notify( osg::DEBUG_INFO ) << + "osgOQ: QG. OQNName: " << _oqnName << + ", Ctx: " << contextID << + ", ID: " << tr->_id << std::endl; +#ifdef _DEBUG + { + GLenum err; + if ((err = glGetError()) != GL_NO_ERROR) + osg::notify( osg::FATAL ) << + "osgOQ: QG: OpenGL error: " << err << "." << std::endl; + } +#endif + + +} + + +unsigned int +QueryGeometry::getNumPixels() +{ + // Usually called during cull, where we do not have a context ID. + // So, instead of returning the per-context result, instead + // find the max pixel count and return it. + int pixels( 0 ); + unsigned int idx; + for( idx=0; idx < _results.size(); idx++ ) + { + TestResult tr = _results[ idx ]; + if( !tr._init ) + continue; + if( tr._numPixels > pixels ) + pixels = tr._numPixels; + } + return( (unsigned int)pixels ); +} + + +void +QueryGeometry::releaseGLObjects( osg::State* state ) +{ + if (!state) + // delete all query IDs for all contexts. + reset(); + + else + { + // Delete all query IDs for the specified context. + unsigned int contextID = state->getContextID(); + TestResult& tr = _results[ contextID ]; + if (tr._contextID == contextID) + { + QueryGeometry::deleteQueryObject( contextID, tr._id ); + tr._init = false; + } + } +} + +void +QueryGeometry::deleteQueryObject( unsigned int contextID, GLuint handle ) +{ + if (handle!=0) + { + OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_mutex_deletedQueryObjectCache ); + + // insert the handle into the cache for the appropriate context. + s_deletedQueryObjectCache[contextID].push_back( handle ); + } +} + + +void +QueryGeometry::flushDeletedQueryObjects( unsigned int contextID, double /*currentTime*/, double& availableTime ) +{ + // if no time available don't try to flush objects. + if (availableTime<=0.0) return; + + const osg::Timer& timer = *osg::Timer::instance(); + osg::Timer_t start_tick = timer.tick(); + double elapsedTime = 0.0; + + { + OpenThreads::ScopedLock lock(s_mutex_deletedQueryObjectCache); + + const osg::Drawable::Extensions* extensions = getExtensions( contextID, true ); + + QueryObjectList& qol = s_deletedQueryObjectCache[contextID]; + + for(QueryObjectList::iterator titr=qol.begin(); + titr!=qol.end() && elapsedTimeglDeleteQueries( 1L, &(*titr ) ); + titr = qol.erase(titr); + elapsedTime = timer.delta_s(start_tick,timer.tick()); + } + } + + availableTime -= elapsedTime; +} + +void +QueryGeometry::discardDeletedQueryObjects( unsigned int contextID ) +{ + OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_mutex_deletedQueryObjectCache ); + QueryObjectList& qol = s_deletedQueryObjectCache[ contextID ]; + qol.clear(); +} + +// End support classes +// + + + + +OcclusionQueryNode::OcclusionQueryNode() + : _enabled( true ), + _visThreshold( 500 ), + _queryFrameCount( 5 ), + _debugBB( false ) +{ + // OQN has two Geode member variables, one for doing the + // query and one for rendering the debug geometry. + // Create and initialize them. + createSupportNodes(); +} + +OcclusionQueryNode::~OcclusionQueryNode() +{ +} + +OcclusionQueryNode::OcclusionQueryNode( const OcclusionQueryNode& oqn, const osg::CopyOp& copyop ) + : Group( oqn, copyop ), + _passed( false ) +{ + _enabled = oqn._enabled; + _visThreshold = oqn._visThreshold; + _queryFrameCount = oqn._queryFrameCount; + _debugBB = oqn._debugBB; + + // Regardless of shallow or deep, create unique support nodes. + createSupportNodes(); +} + + +bool +OcclusionQueryNode::getPassed( const osg::Camera* camera, osg::NodeVisitor& nv ) +{ + if ( !_enabled ) + // Queries are not enabled. The caller should be osgUtil::CullVisitor, + // return true to traverse the subgraphs. + return true; + + { + // Two situations where we want to simply do a regular traversal: + // 1) it's the first frame for this camers + // 2) we haven't rendered for an abnormally long time (probably because we're an out-of-range LOD child) + // In these cases, assume we're visible to avoid blinking. + OpenThreads::ScopedLock lock( _frameCountMutex ); + const int& lastQueryFrame( _frameCountMap[ camera ] ); + if( ( lastQueryFrame == 0 ) || + ( (nv.getTraversalNumber() - lastQueryFrame) > (_queryFrameCount + 1) ) ) + return true; + } + + if (_queryGeode->getDrawable( 0 ) == NULL) + { + osg::notify( osg::FATAL ) << + "osgOQ: OcclusionQueryNode: No QueryGeometry." << std::endl; + // Something's broke. Return true so we at least render correctly. + return true; + } + QueryGeometry* qg = static_cast< QueryGeometry* >( _queryGeode->getDrawable( 0 ) ); + + // Get the near plane for the upcoming distance calculation. + float nearPlane; + const osg::Matrix& proj( camera->getProjectionMatrix() ); + if( ( proj(3,3) != 1. ) || ( proj(2,3) != 0. ) || ( proj(1,3) != 0. ) || ( proj(0,3) != 0.) ) + nearPlane = proj(3,2) / (proj(2,2)-1.); // frustum / perspective + else + nearPlane = (proj(3,2)+1.) / proj(2,2); // ortho + + // If the distance from the near plane to the bounding sphere shell is positive, retrieve + // the results. Otherwise (near plane inside the BS shell) we are considered + // to have passed and don't need to retrieve the query. + const osg::BoundingSphere& bs = getBound(); + float distanceToEyePoint = nv.getDistanceToEyePoint( bs._center, false ); + + float distance = distanceToEyePoint - nearPlane - bs._radius; + _passed = ( distance <= 0.f ); + if (!_passed) + { + int result = qg->getNumPixels(); + _passed = ( (unsigned int)(result) > _visThreshold ); + } + + return _passed; +} + +void +OcclusionQueryNode::traverseQuery( const osg::Camera* camera, osg::NodeVisitor& nv ) +{ + bool issueQuery; + { + const int curFrame = nv.getTraversalNumber(); + + OpenThreads::ScopedLock lock( _frameCountMutex ); + int& lastQueryFrame = _frameCountMap[ camera ]; + issueQuery = (curFrame - lastQueryFrame >= _queryFrameCount); + if (issueQuery) + lastQueryFrame = curFrame; + } + if (issueQuery) + _queryGeode->accept( nv ); +} + +void +OcclusionQueryNode::traverseDebug( osg::NodeVisitor& nv ) +{ + if (_debugBB) + // If requested, display the debug geometry + _debugGeode->accept( nv ); +} + +osg::BoundingSphere +OcclusionQueryNode::computeBound() const +{ + { + // Need to make this routine thread-safe. Typically called by the update + // Visitor, or just after the update traversal, but could be called by + // an application thread or by a non-osgViewer application. + OpenThreads::ScopedLock lock( _computeBoundMutex ) ; + + // This is the logical place to put this code, but the method is const. Cast + // away constness to compute the bounding box and modify the query geometry. + osg::OcclusionQueryNode* nonConstThis = const_cast( this ); + + + osg::ComputeBoundsVisitor cbv; + nonConstThis->accept( cbv ); + osg::BoundingBox bb = cbv.getBoundingBox(); + + osg::ref_ptr v = new osg::Vec3Array; + v->resize( 8 ); + (*v)[0] = osg::Vec3( bb._min.x(), bb._min.y(), bb._min.z() ); + (*v)[1] = osg::Vec3( bb._max.x(), bb._min.y(), bb._min.z() ); + (*v)[2] = osg::Vec3( bb._max.x(), bb._min.y(), bb._max.z() ); + (*v)[3] = osg::Vec3( bb._min.x(), bb._min.y(), bb._max.z() ); + (*v)[4] = osg::Vec3( bb._max.x(), bb._max.y(), bb._min.z() ); + (*v)[5] = osg::Vec3( bb._min.x(), bb._max.y(), bb._min.z() ); + (*v)[6] = osg::Vec3( bb._min.x(), bb._max.y(), bb._max.z() ); + (*v)[7] = osg::Vec3( bb._max.x(), bb._max.y(), bb._max.z() ); + + osg::Geometry* geom = static_cast< osg::Geometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) ); + geom->setVertexArray( v.get() ); + + geom = static_cast< osg::Geometry* >( nonConstThis->_debugGeode->getDrawable( 0 ) ); + geom->setVertexArray( v.get() ); + } + + return Group::computeBound(); +} + + +// Should only be called outside of cull/draw. No thread issues. +void +OcclusionQueryNode::setQueriesEnabled( bool enable ) +{ + _enabled = enable; +} + +// Should only be called outside of cull/draw. No thread issues. +void +OcclusionQueryNode::setDebugDisplay( bool debug ) +{ + _debugBB = debug; +} +bool +OcclusionQueryNode::getDebugDisplay() const +{ + return _debugBB; +} + + + +void +OcclusionQueryNode::setQueryStateSet( osg::StateSet* ss ) +{ + if (!_queryGeode) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl; + return; + } + + _queryGeode->setStateSet( ss ); +} +osg::StateSet* +OcclusionQueryNode::getQueryStateSet() +{ + if (!_queryGeode) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl; + return NULL; + } + return _queryGeode->getStateSet(); +} + +const osg::StateSet* +OcclusionQueryNode::getQueryStateSet() const +{ + if (!_queryGeode) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid query support node." << std::endl; + return NULL; + } + return _queryGeode->getStateSet(); +} + +void +OcclusionQueryNode::setDebugStateSet( osg::StateSet* ss ) +{ + if (!_debugGeode) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl; + return; + } + _debugGeode->setStateSet( ss ); +} + +osg::StateSet* +OcclusionQueryNode::getDebugStateSet() +{ + if (!_debugGeode.valid()) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl; + return NULL; + } + return _debugGeode->getStateSet(); +} +const osg::StateSet* +OcclusionQueryNode::getDebugStateSet() const +{ + if (!_debugGeode.valid()) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid debug support node." << std::endl; + return NULL; + } + return _debugGeode->getStateSet(); +} + +bool +OcclusionQueryNode::getPassed() const +{ + return _passed; +} + + +void +OcclusionQueryNode::createSupportNodes() +{ + GLushort indices[] = { 0, 1, 2, 3, 4, 5, 6, 7, + 0, 3, 6, 5, 2, 1, 4, 7, + 5, 4, 1, 0, 2, 7, 6, 3 }; + + { + // Add the test geometry Geode + _queryGeode = new osg::Geode; + _queryGeode->setName( "OQTest" ); + _queryGeode->setDataVariance( osg::Object::DYNAMIC ); + + osg::ref_ptr< QueryGeometry > geom = new QueryGeometry( getName() ); + geom->setDataVariance( osg::Object::DYNAMIC ); + geom->addPrimitiveSet( new osg::DrawElementsUShort( + osg::PrimitiveSet::QUADS, 24, indices ) ); + + _queryGeode->addDrawable( geom.get() ); + } + + { + // Add a Geode that is a visual representation of the + // test geometry for debugging purposes + _debugGeode = new osg::Geode; + _debugGeode->setName( "Debug" ); + _debugGeode->setDataVariance( osg::Object::DYNAMIC ); + + osg::ref_ptr geom = new osg::Geometry; + geom->setDataVariance( osg::Object::DYNAMIC ); + + osg::ref_ptr ca = new osg::Vec4Array; + ca->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) ); + geom->setColorArray( ca.get() ); + geom->setColorBinding( osg::Geometry::BIND_OVERALL ); + + geom->addPrimitiveSet( new osg::DrawElementsUShort( + osg::PrimitiveSet::QUADS, 24, indices ) ); + + _debugGeode->addDrawable( geom.get() ); + } + + // Creste state sets. Note that the osgOQ visitors (which place OQNs throughout + // the scene graph) create a single instance of these StateSets shared + // between all OQNs for efficiency. + setQueryStateSet( initOQState() ); + setDebugStateSet( initOQDebugState() ); +} + + +void +OcclusionQueryNode::releaseGLObjects( osg::State* state ) const +{ + if(_queryGeode->getDrawable( 0 ) != NULL) + { + // Query object discard and deletion is handled by QueryGeometry support class. + OcclusionQueryNode* nonConstThis = const_cast< OcclusionQueryNode* >( this ); + QueryGeometry* qg = static_cast< QueryGeometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) ); + qg->releaseGLObjects( state ); + } +} + +void +OcclusionQueryNode::flushDeletedQueryObjects( unsigned int contextID, double currentTime, double& availableTime ) +{ + // Query object discard and deletion is handled by QueryGeometry support class. + QueryGeometry::flushDeletedQueryObjects( contextID, currentTime, availableTime ); +} + +void +OcclusionQueryNode::discardDeletedQueryObjects( unsigned int contextID ) +{ + // Query object discard and deletion is handled by QueryGeometry support class. + QueryGeometry::discardDeletedQueryObjects( contextID ); +} + + +}