diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e517e7784..c915d19de 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -52,6 +52,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osgmovie) ADD_SUBDIRECTORY(osgmultitexture) ADD_SUBDIRECTORY(osgoccluder) + ADD_SUBDIRECTORY(osgocclusionquery) ADD_SUBDIRECTORY(osgpagedlod) ADD_SUBDIRECTORY(osgparametric) ADD_SUBDIRECTORY(osgparticle) diff --git a/examples/osgocclusionquery/CMakeLists.txt b/examples/osgocclusionquery/CMakeLists.txt new file mode 100644 index 000000000..ebcd2d987 --- /dev/null +++ b/examples/osgocclusionquery/CMakeLists.txt @@ -0,0 +1,7 @@ +#this file is automatically generated + + +SET(TARGET_SRC osgocclusionquery.cpp ) +SET(TARGET_ADDED_LIBRARIES osgUtil osgDB ) +#### end var setup ### +SETUP_EXAMPLE(osgocclusionquery) diff --git a/examples/osgocclusionquery/osgocclusionquery.cpp b/examples/osgocclusionquery/osgocclusionquery.cpp new file mode 100644 index 000000000..35133d81e --- /dev/null +++ b/examples/osgocclusionquery/osgocclusionquery.cpp @@ -0,0 +1,850 @@ +/* OpenSceneGraph example, osganimate. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +// This exampl demonstrates use of OcclusionQueryNode. +// +// In general, you use OcclusionQueryNode by simply attaching a subgraph +// or subgraphs as children, and it performs an OpenGL oclusion query +// to determine whether to draw the subgraphs or not. +// +// You can manually insert OcclusionQueryNodes at strategic locations +// in your scene graph, or you can write a NodeVisitor to insert them +// automatically, as this example shows. +// +// Run this example with no command line arguments, and it creates +// a "stock scene" to show how OcclusionQueryNode can be used. +// +// Or, run this example with a model on the command line, and the +// example uses a NodeVisitor to try to find worthwhile locations +// for OcclusionQueryNodes in your the scene graph. + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + + +// NodeVisitors and utility functions for OcclusionQueryNode + +// 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; +} + +// Use this visitor to insert OcclusionQueryNodes (OQNs) in the +// visited subgraph. Only one OQN will test any particular node +// (no nesting). See also OcclusionQueryNonFlatVisitor. +class OcclusionQueryVisitor : public osg::NodeVisitor +{ +public: + OcclusionQueryVisitor(); + virtual ~OcclusionQueryVisitor(); + + // Specify the vertex count threshold for performing occlusion + // query tests. Nodes in the scene graph whose total child geometry + // contains fewer vertices than the specified threshold will + // never be tested, just drawn. (In fact, they will br treated as + // potential occluders and rendered first in front-to-back order.) + void setOccluderThreshold( int vertices ); + int getOccluderThreshold() const; + + virtual void apply( osg::OcclusionQueryNode& oqn ); + virtual void apply( osg::Group& group ); + virtual void apply( osg::Geode& geode ); + +protected: + void addOQN( osg::Node& node ); + + // When an OQR creates all OQNs and each OQN shares the same OQC, + // these methods are used to uniquely name all OQNs. Handy + // for debugging. + std::string getNextOQNName(); + int getNameIdx() const { return _nameIdx; } + + osg::ref_ptr _state; + osg::ref_ptr _debugState; + + unsigned int _nameIdx; + + int _occluderThreshold; +}; + +// Find all OQNs in the visited scene graph and set their visibility threshold. +class VisibilityThresholdVisitor : public osg::NodeVisitor +{ +public: + VisibilityThresholdVisitor( unsigned int threshold=500 ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), + _visThreshold( threshold ) {} + virtual ~VisibilityThresholdVisitor() {} + + virtual void apply( osg::OcclusionQueryNode& oqn ); + +protected: + unsigned int _visThreshold; +}; + +// Find all OQNs in the visited scene graph and set the number of frames +// between queries. +class QueryFrameCountVisitor : public osg::NodeVisitor +{ +public: + QueryFrameCountVisitor( int count=5 ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), + _count( count ) {} + virtual ~QueryFrameCountVisitor() {} + + virtual void apply( osg::OcclusionQueryNode& oqn ); + +protected: + unsigned int _count; +}; + +// Find all OQNs in the visited scene graph and enable or disable queries.. +class EnableQueryVisitor : public osg::NodeVisitor +{ +public: + EnableQueryVisitor( bool enable=true ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), + _enabled( enable ) {} + virtual ~EnableQueryVisitor() {} + + virtual void apply( osg::OcclusionQueryNode& oqn ); + +protected: + bool _enabled; +}; + +// Find all OQNs in the visited scene graph and enable or disable the +// debug bounding volume display. +class DebugDisplayVisitor : public osg::NodeVisitor +{ +public: + DebugDisplayVisitor( bool debug=true ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), + _debug( debug ) {} + virtual ~DebugDisplayVisitor() {} + + virtual void apply( osg::OcclusionQueryNode& oqn ); + +protected: + bool _debug; +}; + +// Remove all OQNs from the visited scene graph. +class RemoveOcclusionQueryVisitor : public osg::NodeVisitor +{ +public: + RemoveOcclusionQueryVisitor(); + virtual ~RemoveOcclusionQueryVisitor(); + + virtual void apply( osg::OcclusionQueryNode& oqn ); + +protected: +}; + +// Gather statistics about OQN performance in the visited scene graph. +class StatisticsVisitor : public osg::NodeVisitor +{ +public: + StatisticsVisitor( osg::NodeVisitor::TraversalMode mode=osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN ); + virtual ~StatisticsVisitor(); + + virtual void apply( osg::OcclusionQueryNode& oqn ); + + void reset(); + unsigned int getNumOQNs() const; + unsigned int getNumPassed() const; + +protected: + unsigned int _numOQNs; + unsigned int _numPassed; +}; + + +unsigned int countGeometryVertices( osg::Geometry* geom ) +{ + if (!geom->getVertexArray()) + return 0; + + // TBD This will eventually iterate over the PrimitiveSets and total the + // number of vertices actually used. But for now, it just returns the + // size of the vertex array. + + return geom->getVertexArray()->getNumElements(); +} + +class VertexCounter : public osg::NodeVisitor +{ +public: + VertexCounter( int limit ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), + _limit( limit ), + _total( 0 ) {} + ~VertexCounter() {} + + int getTotal() { return _total; } + bool exceeded() const { return _total > _limit; } + void reset() { _total = 0; } + + virtual void apply( osg::Node& node ) + { + // Check for early abort. If out total already exceeds the + // max number of vertices, no need to traverse further. + if (exceeded()) + return; + traverse( node ); + } + + virtual void apply( osg::Geode& geode ) + { + // Possible early abort. + if (exceeded()) + return; + + unsigned int i; + for( i = 0; i < geode.getNumDrawables(); i++ ) + { + osg::Geometry* geom = dynamic_cast(geode.getDrawable(i)); + if( !geom ) + continue; + + _total += countGeometryVertices( geom ); + + if (_total > _limit) + break; + } + } + +protected: + int _limit; + int _total; +}; + + + +OcclusionQueryVisitor::OcclusionQueryVisitor() + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), + _nameIdx( 0 ), + _occluderThreshold( 5000 ) +{ + // Init StateSet for occlusion query geometry. + _state = initOQState(); + // Initialize StateSet for debug geometry + _debugState = initOQDebugState(); +} + +OcclusionQueryVisitor::~OcclusionQueryVisitor() +{ + osg::notify( osg::INFO ) << + "osgOQ: OcclusionQueryVisitor: Added " << getNameIdx() << + " OQNodes." << std::endl; +} + +void +OcclusionQueryVisitor::setOccluderThreshold( int vertices ) +{ + _occluderThreshold = vertices; +} +int +OcclusionQueryVisitor::getOccluderThreshold() const +{ + return _occluderThreshold; +} + +void +OcclusionQueryVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + // A subgraph is already under osgOQ control. + // Don't traverse further. + return; +} + +void +OcclusionQueryVisitor::apply( osg::Group& group ) +{ + if (group.getNumParents() == 0) + { + // Can't add an OQN above a root node. + traverse( group ); + return; + } + + int preTraverseOQNCount = getNameIdx(); + traverse( group ); + + if (getNameIdx() > preTraverseOQNCount) + // A least one OQN was added below the current node. + // Don't add one here to avoid hierarchical nesting. + return; + + // There are no OQNs below this group. If the vertex + // count exceeds the threshold, add an OQN here. + addOQN( group ); +} + +void +OcclusionQueryVisitor::apply( osg::Geode& geode ) +{ + if (geode.getNumParents() == 0) + { + // Can't add an OQN above a root node. + traverse( geode ); + return; + } + + addOQN( geode ); +} + +void +OcclusionQueryVisitor::addOQN( osg::Node& node ) +{ + VertexCounter vc( _occluderThreshold ); + node.accept( vc ); + if (vc.exceeded()) + { + // Insert OQN(s) above this node. + unsigned int np = node.getNumParents(); + while (np--) + { + osg::Group* parent = dynamic_cast( node.getParent( np ) ); + if (parent != NULL) + { + osg::ref_ptr oqn = new osg::OcclusionQueryNode(); + oqn->addChild( &node ); + parent->replaceChild( &node, oqn.get() ); + + oqn->setName( getNextOQNName() ); + // Set all OQNs to use the same query StateSets (instead of multiple copies + // of the same StateSet) for efficiency. + oqn->setQueryStateSets( _state.get(), _debugState.get() ); + } + } + } +} + +std::string +OcclusionQueryVisitor::getNextOQNName() +{ + std::ostringstream ostr; + ostr << "OQNode_" << _nameIdx++; + return ostr.str(); +} + + + + +// +void +VisibilityThresholdVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + oqn.setVisibilityThreshold( _visThreshold ); + + traverse( oqn ); +} + +void +QueryFrameCountVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + oqn.setQueryFrameCount( _count ); + + traverse( oqn ); +} + +void +EnableQueryVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + oqn.setQueriesEnabled( _enabled ); + + traverse( oqn ); +} + + +void +DebugDisplayVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + oqn.setDebugDisplay( _debug ); + + traverse( oqn ); +} + + +RemoveOcclusionQueryVisitor::RemoveOcclusionQueryVisitor() + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ) +{ +} + +RemoveOcclusionQueryVisitor::~RemoveOcclusionQueryVisitor() +{ +} + +void +RemoveOcclusionQueryVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + if (oqn.getNumParents() == 0) + { + // Even if this is an OQN, can't delete it because it's the root. + traverse( oqn ); + return; + } + + osg::ref_ptr oqnPtr = &oqn; + + unsigned int np = oqn.getNumParents(); + while (np--) + { + osg::Group* parent = dynamic_cast( oqn.getParent( np ) ); + if (parent != NULL) + { + // Remove OQN from parent. + parent->removeChild( oqnPtr.get() ); + + // Add OQN's children to parent. + unsigned int nc = oqn.getNumChildren(); + while (nc--) + parent->addChild( oqn.getChild( nc ) ); + } + } +} + + + +StatisticsVisitor::StatisticsVisitor( osg::NodeVisitor::TraversalMode mode ) + : osg::NodeVisitor( mode ), + _numOQNs( 0 ), + _numPassed( 0 ) +{ +} + +StatisticsVisitor::~StatisticsVisitor() +{ +} + +void +StatisticsVisitor::apply( osg::OcclusionQueryNode& oqn ) +{ + _numOQNs++; + if (oqn.getPassed()) + _numPassed++; + + traverse( oqn ); +} + +void +StatisticsVisitor::reset() +{ + _numOQNs = _numPassed = 0; +} + +unsigned int +StatisticsVisitor::getNumOQNs() const +{ + return _numOQNs; +} +unsigned int +StatisticsVisitor::getNumPassed() const +{ + return _numPassed; +} + +// End NodeVisitors + + + +// KetHandler -- +// Allow user to do interesting things with an +// OcclusionQueryNode-enabled scene graph at run time. +class KeyHandler : public osgGA::GUIEventHandler +{ +public: + KeyHandler( osg::Node& node ) + : _node( node ), + _enable( true ), + _debug( false ) + {} + + bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& ) + { + switch( ea.getEventType() ) + { + case(osgGA::GUIEventAdapter::KEYUP): + { + if (ea.getKey()==osgGA::GUIEventAdapter::KEY_F6) + { + // F6 -- Toggle osgOQ testing. + _enable = !_enable; + EnableQueryVisitor eqv( _enable ); + _node.accept( eqv ); + return true; + } + else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_F7) + { + // F7 -- Toggle display of OQ test bounding volumes + _debug = !_debug; + DebugDisplayVisitor ddv( _debug ); + _node.accept( ddv ); + return true; + } + else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_F8) + { + // F8 -- Gether stats and display + StatisticsVisitor sv; + _node.accept( sv ); + std::cout << "osgOQ: Stats: numOQNs " << sv.getNumOQNs() << ", numPased " << sv.getNumPassed() << std::endl; + return true; + } + else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_F9) + { + // F9 -- Remove all OcclusionQueryNodes + RemoveOcclusionQueryVisitor roqv; + _node.accept( roqv ); + return true; + } + else if (ea.getKey()=='o') + { + if (osgDB::writeNodeFile( _node, "saved_model.osg" )) + osg::notify( osg::ALWAYS ) << "osgOQ: Wrote scene graph to \"saved_model.osg\"" << std::endl; + else + osg::notify( osg::ALWAYS ) << "osgOQ: Wrote failed for \"saved_model.osg\"" << std::endl; + return true; + } + return false; + } + default: + break; + } + return false; + } + + osg::Node& _node; + + bool _enable, _debug; +}; + +// Create a cube with one side missing. This makes a great simple occluder. +osg::ref_ptr +createBox() +{ + osg::ref_ptr box = new osg::Geode; + + osg::StateSet* state = box->getOrCreateStateSet(); + osg::PolygonMode* pm = new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL ); + state->setAttributeAndModes( pm, + osg::StateAttribute::ON | osg::StateAttribute::PROTECTED ); + + osg::ref_ptr geom = new osg::Geometry; + osg::ref_ptr v = new osg::Vec3Array; + geom->setVertexArray( v.get() ); + + { + const float x( 0.f ); + const float y( 0.f ); + const float z( 0.f ); + const float r( 1.1f ); + + v->push_back( osg::Vec3( x-r, y-r, z-r ) ); //left -X + v->push_back( osg::Vec3( x-r, y-r, z+r ) ); + v->push_back( osg::Vec3( x-r, y+r, z+r ) ); + v->push_back( osg::Vec3( x-r, y+r, z-r ) ); + + v->push_back( osg::Vec3( x+r, y-r, z+r ) ); //right +X + v->push_back( osg::Vec3( x+r, y-r, z-r ) ); + v->push_back( osg::Vec3( x+r, y+r, z-r ) ); + v->push_back( osg::Vec3( x+r, y+r, z+r ) ); + + v->push_back( osg::Vec3( x-r, y-r, z-r ) ); // bottom -Z + v->push_back( osg::Vec3( x-r, y+r, z-r ) ); + v->push_back( osg::Vec3( x+r, y+r, z-r ) ); + v->push_back( osg::Vec3( x+r, y-r, z-r ) ); + + v->push_back( osg::Vec3( x-r, y-r, z+r ) ); // top +Z + v->push_back( osg::Vec3( x+r, y-r, z+r ) ); + v->push_back( osg::Vec3( x+r, y+r, z+r ) ); + v->push_back( osg::Vec3( x-r, y+r, z+r ) ); + + v->push_back( osg::Vec3( x-r, y+r, z-r ) ); // back +Y + v->push_back( osg::Vec3( x-r, y+r, z+r ) ); + v->push_back( osg::Vec3( x+r, y+r, z+r ) ); + v->push_back( osg::Vec3( x+r, y+r, z-r ) ); + } + + osg::ref_ptr c = new osg::Vec4Array; + geom->setColorArray( c.get() ); + geom->setColorBinding( osg::Geometry::BIND_OVERALL ); + c->push_back( osg::Vec4( 0.f, 1.f, 1.f, 1.f ) ); + + osg::ref_ptr n = new osg::Vec3Array; + geom->setNormalArray( n.get() ); + geom->setNormalBinding( osg::Geometry::BIND_PER_PRIMITIVE ); + n->push_back( osg::Vec3( -1.f, 0.f, 0.f ) ); + n->push_back( osg::Vec3( 1.f, 0.f, 0.f ) ); + n->push_back( osg::Vec3( 0.f, 0.f, -1.f ) ); + n->push_back( osg::Vec3( 0.f, 0.f, 1.f ) ); + n->push_back( osg::Vec3( 0.f, 1.f, 0.f ) ); + + geom->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 20 ) ); + box->addDrawable( geom.get() ); + + return box.get(); +} + +// Make a Geometry that renders slow intentionally. +// To make sure it renders slow, we do the following: +// * Disable display lists +// * Force glBegin/glEnd slow path +// * Lots of vertices and color data per vertex +// * No vertex sharing +osg::ref_ptr +createRandomTriangles( unsigned int num ) +{ + osg::ref_ptr tris = new osg::Geode; + + osg::StateSet* state = tris->getOrCreateStateSet(); + osg::StateSet* ss = tris->getOrCreateStateSet(); + // Force wireframe. Many gfx cards handle this poorly. + osg::PolygonMode* pm = new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE ); + ss->setAttributeAndModes( pm, osg::StateAttribute::ON | + osg::StateAttribute::PROTECTED); + ss->setMode( GL_LIGHTING, osg::StateAttribute::OFF | + osg::StateAttribute::PROTECTED); + + osg::ref_ptr geom = new osg::Geometry; + // Disable display lists to decrease performance. + geom->setUseDisplayList( false ); + + osg::ref_ptr v = new osg::Vec3Array; + geom->setVertexArray( v.get() ); + v->resize( num*3 ); + + unsigned int i; + srand( 0 ); +#define RAND_NEG1_TO_1 ( ((rand()%20)-10)*.1 ) + for (i=0; i c = new osg::Vec4Array; + geom->setColorArray( c.get() ); + // Bind per primitive to force slow glBegin/glEnd path. + geom->setColorBinding( osg::Geometry::BIND_PER_PRIMITIVE ); + c->resize( num ); + +#define RAND_0_TO_1 ( (rand()%10)*.1 ) + for (i=0; iaddPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 0, num*3 ) ); + tris->addDrawable( geom.get() ); + + return tris.get(); +} + +// Create the stock scene: +// Top level Group +// Geode (simple occluder +// OcclusionQueryNode +// Geode with complex, slow geometry. +osg::ref_ptr +createStockScene() +{ + // Create a simple box occluder + osg::ref_ptr root = new osg::Group(); + root->addChild( createBox().get() ); + + // Create a complex mess of triangles as a child below an + // OcclusionQueryNode. The OQN will ensure that the + // subgraph isn't rendered when it's not visible. + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->addChild( createRandomTriangles( 20000 ).get() ); + root->addChild( oqn.get() ); + + return root.get(); +} + + +int main(int argc, char** argv) +{ + // use an ArgumentParser object to manage the program arguments. + osg::ArgumentParser arguments(&argc,argv); + + arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName()); + arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" demonstrates OpenGL occlusion query in OSG using the OcclusionQueryNode."); + arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] [filename(s)]"); + arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters"); + + // if user request help write it out to cout. + if (arguments.read("-h") || arguments.read("--help")) + { + arguments.getApplicationUsage()->write(std::cout, osg::ApplicationUsage::COMMAND_LINE_OPTION); + return 1; + } + + // report any errors if they have occurred when parsing the program arguments. + if (arguments.errors()) + { + arguments.writeErrorMessages(std::cout); + return 1; + } + + osgViewer::Viewer viewer( arguments ); + + // add the state manipulator + viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) ); + + // add the stats handler + viewer.addEventHandler(new osgViewer::StatsHandler); + + // add the help handler + viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage())); + + + // load the specified model + osg::ref_ptr root = osgDB::readNodeFiles( arguments ); + if (!root) + { + std::cout << arguments.getApplicationName() <<": No files specified, or can't load them." << std::endl; + root = createStockScene().get(); + if (!root) + { + std::cout << arguments.getApplicationName() <<": Failed to create stock scene." << std::endl; + return 1; + } + std::cout << "Using stock scene instead." << std::endl; + } + else + { + // Run a NodeVisitor to insert OcclusionQueryNodes in the scene graph. + OcclusionQueryVisitor oqv; + root->accept( oqv ); + } + + bool optimize = arguments.read( "--opt" ); + + // any option left unread are converted into errors to write out later. + arguments.reportRemainingOptionsAsUnrecognized(); + + // report any errors if they have occurred when parsing the program arguments. + if (arguments.errors()) + { + arguments.writeErrorMessages(std::cout); + return 1; + } + + + // optimize the scene graph, remove redundant nodes and state etc. + if (optimize) + { + osgUtil::Optimizer optimizer; + optimizer.optimize( root.get() ); + } + + viewer.setSceneData( root.get() ); + + KeyHandler* kh = new KeyHandler( *root ); + viewer.addEventHandler( kh ); + + return viewer.run(); +} diff --git a/include/osg/NodeVisitor b/include/osg/NodeVisitor index 51706acba..b78d20a83 100644 --- a/include/osg/NodeVisitor +++ b/include/osg/NodeVisitor @@ -30,6 +30,7 @@ class LightSource; class LOD; class MatrixTransform; class OccluderNode; +class OcclusionQueryNode; class PagedLOD; class PositionAttitudeTransform; class Projection; @@ -256,6 +257,7 @@ class OSG_EXPORT NodeVisitor : public virtual Referenced virtual void apply(PagedLOD& node) { apply((LOD&)node); } virtual void apply(ClearNode& node) { apply((Group&)node); } virtual void apply(OccluderNode& node) { apply((Group&)node); } + virtual void apply(OcclusionQueryNode& node) { apply((Group&)node); } /** Callback for managing database paging, such as generated by PagedLOD nodes.*/ diff --git a/include/osg/OcclusionQueryNode b/include/osg/OcclusionQueryNode new file mode 100644 index 000000000..ba1c54901 --- /dev/null +++ b/include/osg/OcclusionQueryNode @@ -0,0 +1,113 @@ +// +// 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. +// + +#ifndef OSG_OCCLUSION_QUERY_NODE +#define OSG_OCCLUSION_QUERY_NODE 1 + +#include +#include +#include + + + +namespace osg { + + +// This Node performs occlusion query testing on its children. +// You can use it directly to occlusion query test a portion +// of your scene graph, or you can use it implicitly with an +// OcclusionQueryRoot, which places OcclusionQueryNodes where +// needed and acts as a master control. +class OSG_EXPORT OcclusionQueryNode : public osg::Group +{ +public: + OcclusionQueryNode(); + + // Copy constructor using CopyOp to manage deep vs shallow copy. + OcclusionQueryNode( const OcclusionQueryNode& oqn, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY ); + + META_Node( osg, OcclusionQueryNode ); + + virtual osg::BoundingSphere computeBound() const; + + // When disabled, OQN doesn't perform occlusion queries, and simply + // renders its children. + void setQueriesEnabled( bool enable=true ); + bool getQueriesEnabled() const { return _enabled; } + + + // Sets/gets the visibility threshold. If the test indicates that + // the number of visible pixels is less than the specified + // threshold, don't draw the actual geometry. + void setVisibilityThreshold( unsigned int pixels ) { _visThreshold = pixels; } + unsigned int getVisibilityThreshold() const { return _visThreshold; } + + // Specifies how many frames to wait before issuing another query. + void setQueryFrameCount( int frames ) { _queryFrameCount = frames; } + int getQueryFrameCount() const { return _queryFrameCount; } + + // Indicate whether or not the bounding box used in the occlusion query test + // should be rendered. Handy for debugging and development. + // Should only be called outside of cull/draw. No thread issues. + void setDebugDisplay( bool enable ); + bool getDebugDisplay() const; + + + // Set the StateSets used by the OQN when rendering OQ geometry + // or debug bounding geometry. + void setQueryStateSets( osg::StateSet* ss, osg::StateSet* ssDebug ); + + // For statistics gathering, e.g., by a NodeVisitor. + bool getPassed() const; + + + // These methods are public so that osgUtil::CullVisitor can access them. + // Not intended for application use. + bool getPassed( const osg::Camera* camera, float distanceToEyePoint ); + void traverseQuery( const osg::Camera* camera, osg::NodeVisitor& nv ); + void traverseDebug( osg::NodeVisitor& nv ); + +protected: + virtual ~OcclusionQueryNode(); + + void createSupportNodes(); + + osg::ref_ptr< osg::Geode > _queryGeode; + osg::ref_ptr< osg::Geode > _debugGeode; + + bool _enabled; + + // Tracks the last frame number that we performed a query. + // User can set how many times (See setQueryFrameCount). + typedef std::map< const osg::Camera*, int > FrameCountMap; + FrameCountMap _frameCountMap; + mutable OpenThreads::Mutex _frameCountMutex; + + // For statistics gathering + bool _passed; + + // User-settable variables + unsigned int _visThreshold; + int _queryFrameCount; + bool _debugBB; + + + // Required to ensure that computeBound() is thread-safe. + mutable OpenThreads::Mutex _computeBoundMutex; +}; + +} + + +#endif diff --git a/include/osgUtil/CullVisitor b/include/osgUtil/CullVisitor index 7cc25e5ec..83e0e9f39 100644 --- a/include/osgUtil/CullVisitor +++ b/include/osgUtil/CullVisitor @@ -91,6 +91,7 @@ class OSGUTIL_EXPORT CullVisitor : public osg::NodeVisitor, public osg::CullStac virtual void apply(osg::ClearNode& node); virtual void apply(osg::Camera& node); virtual void apply(osg::OccluderNode& node); + virtual void apply(osg::OcclusionQueryNode& node); /** Push state set on the current state group. * If the state exists in a child state group of the current diff --git a/src/osg/CMakeLists.txt b/src/osg/CMakeLists.txt index 53bffe2c4..9c7a2d7e8 100644 --- a/src/osg/CMakeLists.txt +++ b/src/osg/CMakeLists.txt @@ -91,6 +91,7 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/Notify ${HEADER_PATH}/Object ${HEADER_PATH}/OccluderNode + ${HEADER_PATH}/OcclusionQueryNode ${HEADER_PATH}/OperationThread ${HEADER_PATH}/PagedLOD ${HEADER_PATH}/Plane @@ -247,6 +248,7 @@ ADD_LIBRARY(${LIB_NAME} Notify.cpp Object.cpp OccluderNode.cpp + OcclusionQueryNode.cpp OperationThread.cpp PagedLOD.cpp Point.cpp diff --git a/src/osg/OcclusionQueryNode.cpp b/src/osg/OcclusionQueryNode.cpp new file mode 100644 index 000000000..a137adf14 --- /dev/null +++ b/src/osg/OcclusionQueryNode.cpp @@ -0,0 +1,686 @@ +// +// 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 + + + +// +// 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.) + + +// 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; +} + + +// TestResult -- stores (per context) results of an occlusion query +// test performed by QueryGeometry. An OcclusionQueryNode has a +// Geode owning a single QueryGeometry that +// draws the occlusion query geometry. QueryGeometry keeps a +// TestResult per context to store the result/status of each query. +// Accessed during the cull and draw traversals. +class TestResult : public osg::Referenced +{ +public: + TestResult() : _init( false ), _id( 0 ), _active( false ), _numPixels( 0 ) {} + ~TestResult() {} + + bool _init; + + // Query ID for this context. + GLuint _id; + + // Set to true when a query gets issued and set to + // false when the result is retrieved. + mutable bool _active; + + // Result of last query. + GLint _numPixels; +}; + +// QueryGeometry -- A Drawable that performs an occlusion query, +// using its geometric data as the query geometry. +class QueryGeometry : public osg::Geometry +{ +public: + QueryGeometry( const std::string& oqnName=std::string("") ); + ~QueryGeometry() {} + + // TBD implement copy constructor + + virtual void drawImplementation( osg::RenderInfo& renderInfo ) const; + + unsigned int getNumPixels( const osg::Camera* cam ); + +protected: + typedef std::map< const osg::Camera*, TestResult > ResultMap; + mutable ResultMap _results; + mutable OpenThreads::Mutex _mapMutex; + + // Needed for debug only + std::string _oqnName; +}; + +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()) + { + 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; + + 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( TestResult* tr ) + { + _results.push_back( tr ); + } + + osg::Drawable::Extensions* getExtensions( unsigned int contextID, bool createIfNotInitalized ) + { + if (!s_extensions[ contextID ] && createIfNotInitalized) + s_extensions[ contextID ] = new osg::Drawable::Extensions( contextID ); + return s_extensions[ contextID ].get(); + } + + typedef osg::buffered_value< osg::ref_ptr< osg::Drawable::Extensions > > BufferedExtensions; + static BufferedExtensions s_extensions; + + osg::Drawable::Extensions* _extensionsFallback; +}; + +RetrieveQueriesCallback::BufferedExtensions RetrieveQueriesCallback::s_extensions; + + + +// 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& camera) const + { + if (!_rqcb) + { + osg::notify( osg::FATAL ) << "oagOQ: CQCB: Invalid RQCB." << std::endl; + return; + } + _rqcb->reset(); + } + + RetrieveQueriesCallback* _rqcb; +}; + + +QueryGeometry::QueryGeometry( const std::string& oqnName ) + : _oqnName( oqnName ) +{ + // TBD check to see if we can have this on. + setUseDisplayList( false ); +} + +// 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 ) << "oagOQ: QG: Invalid RQCB." << std::endl; + return; + } + rqcb->add( tr ); + + + // Issue query + if (!tr->_init) + { + ext->glGenQueries( 1, &(tr->_id) ); + tr->_init = true; + } + + osg::notify( osg::DEBUG_INFO ) << + "oagOQ: QG: Querying for: " << _oqnName << std::endl; + + ext->glBeginQuery( GL_SAMPLES_PASSED_ARB, tr->_id ); + 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; +} + +// End support classes +// + + + +namespace osg +{ + + +OcclusionQueryNode::OcclusionQueryNode() + : _enabled( true ), + _visThreshold( 500 ), + _queryFrameCount( 5 ), + _debugBB( false ) +{ + setDataVariance( osg::Object::DYNAMIC ); + +#if 0 + // Before integration into core OSG, OcclusionQueryNode supported a config + // file to override default values. Can't include this in core osg as it + // would introduce a dependency onto osgDB for finding the config file. + + // Override defaults if specified in the config file using the OptionLoader singleton. + int clampMe; + if ( OptionLoader::instance()->getOption( "VisibilityThreshold", clampMe ) ) + _visThreshold = (clampMe < 0 ) ? 0 : static_cast( clampMe ); + if ( OptionLoader::instance()->getOption( "QueryFrameCount", clampMe ) ) + _queryFrameCount = (clampMe < 1) ? 1 : static_cast( clampMe ); + OptionLoader::instance()->getOption( "DebugBoundingVolume", _debugBB ); +#endif + + // 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 ) +{ + _enabled = oqn._enabled; + _debugBB = oqn._debugBB; + + // Regardless of shallow or deep, create unique support nodes. + createSupportNodes(); +} + +//PORT-TBD +#if 0 +// Currently, retrieves last frame's query results during current frame's cull traversal. +// In the future, should retrive last frame's results immediately after last +// frame's buffer swap. +void +OcclusionQueryNode::traverse( osg::NodeVisitor& nv ) +{ + if ( !_enabled || (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR) ) + { + osg::Group::traverse( nv ); + return; + } + + osgUtil::CullVisitor* cv = dynamic_cast( &nv ); + osg::Camera* camera = cv->getRenderStage()->getCamera(); + + + // In the future, we could hold a reference directly to the QueryDrawable + // to avoid the dynamic_cast. + QueryGeometry* qg = dynamic_cast< QueryGeometry* >( _queryGeode->getDrawable( 0 ) ); + if (qg == NULL) + { + osg::notify( osg::FATAL ) << + "osgOQ: OcclusionQueryNode: No QueryGeometry." << std::endl; + return; + } + + // If the distance to the bounding sphere shell is positive, retrieve + // the results. Others (we're inside the BS shell) we are considered + // to have passed and don't need to retrieve the query. + const osg::BoundingSphere& bs = getBound(); + float distance = cv->getDistanceToEyePoint( bs._center, false ) - bs._radius; + _passed = ( distance <= 0.f ); + if (!_passed) + { + int result = qg->getNumPixels( camera ); + _passed = ( (unsigned int)(result) > _visThreshold ); + } + + if (_passed) + // We're visible. Traverse our children + osg::Group::traverse( nv ); + + // Submit a new query only if sufficient frames have elapsed. + bool issueQuery; + { + const int curFrame = nv.getTraversalNumber(); + + OpenThreads::ScopedLock lock( _frameCountMutex ); + int& lastQueryFrame = _frameCountMap[ camera ]; + if ( issueQuery = (curFrame - lastQueryFrame >= _queryFrameCount) ) + lastQueryFrame = curFrame; + } + if (issueQuery) + _queryGeode->accept( nv ); + + if (_debugBB) + // If requested, display the debug geometry + _debugGeode->accept( nv ); + + osg::notify( osg::DEBUG_INFO ) << + "oagOQ: OQN::traverse: OQN: " << getName() << + ", Cam: " << camera << + ", Passed: " << _passed << std::endl; +} +#endif + +bool +OcclusionQueryNode::getPassed( const osg::Camera* camera, float distanceToEyePoint ) +{ + if ( !_enabled ) + // Queries are not enabled. The caller should be osgUtil::CullVisitor, + // return true to traverse the subgraphs. + return true; + + // In the future, we could hold a reference directly to the QueryDrawable + // to avoid the dynamic_cast. + QueryGeometry* qg = dynamic_cast< QueryGeometry* >( _queryGeode->getDrawable( 0 ) ); + if (qg == NULL) + { + osg::notify( osg::FATAL ) << + "osgOQ: OcclusionQueryNode: No QueryGeometry." << std::endl; + // Something's broke. Return true so we at least render correctly. + return true; + } + + // If the distance to the bounding sphere shell is positive, retrieve + // the results. Others (we're inside the BS shell) we are considered + // to have passed and don't need to retrieve the query. + const osg::BoundingSphere& bs = getBound(); + float distance = distanceToEyePoint - 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 ]; + if ( issueQuery = (curFrame - lastQueryFrame >= _queryFrameCount) ) + 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 = dynamic_cast< osg::Geometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) ); + geom->setVertexArray( v.get() ); + + geom = dynamic_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::setQueryStateSets( osg::StateSet* ss, osg::StateSet* ssDebug ) +{ + if (!_queryGeode.valid() || !_debugGeode.valid()) + { + osg::notify( osg::WARN ) << "osgOQ: OcclusionQueryNode:: Invalid support node(s)." << std::endl; + return; + } + + _queryGeode->setStateSet( ss ); + _debugGeode->setStateSet( ssDebug ); +} + +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. + setQueryStateSets( initOQState(), initOQDebugState() ); +} + + +} diff --git a/src/osgPlugins/osg/CMakeLists.txt b/src/osgPlugins/osg/CMakeLists.txt index b8d08f779..229bb8059 100644 --- a/src/osgPlugins/osg/CMakeLists.txt +++ b/src/osgPlugins/osg/CMakeLists.txt @@ -42,6 +42,7 @@ Node.cpp NodeCallback.cpp Object.cpp OccluderNode.cpp +OcclusionQueryNode.cpp PagedLOD.cpp Point.cpp PointSprite.cpp diff --git a/src/osgUtil/CullVisitor.cpp b/src/osgUtil/CullVisitor.cpp index 73655cbaa..82039b964 100644 --- a/src/osgUtil/CullVisitor.cpp +++ b/src/osgUtil/CullVisitor.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -1392,5 +1393,34 @@ void CullVisitor::apply(osg::OccluderNode& node) popOccludersCurrentMask(_nodePath); } +void CullVisitor::apply(osg::OcclusionQueryNode& node) +{ + if (isCulled(node)) return; + + // push the culling mode. + pushCurrentMask(); + + // push the node's state. + StateSet* node_state = node.getStateSet(); + if (node_state) pushStateSet(node_state); + osg::Camera* camera = getRenderStage()->getCamera(); + // If previous query indicates visible, then traverse as usual. + if (node.getPassed( camera, getDistanceToEyePoint( node.getBound()._center, false ) )) + handle_cull_callbacks_and_traverse(node); + + // Traverse the query subtree if OcclusionQueryNode needs to issue another query. + node.traverseQuery( camera, *this ); + + // Traverse the debug bounding geometry, if enabled. + node.traverseDebug( *this ); + + + // pop the node's state off the render graph stack. + if (node_state) popStateSet(); + + // pop the culling mode. + popCurrentMask(); +} +