From Paul Martz, Introduced osg::OcclusionQueryNode with support for OpenGL occlusion query extension
This commit is contained in:
parent
5f7a2968ac
commit
4889342c4a
@ -52,6 +52,7 @@ IF(DYNAMIC_OPENSCENEGRAPH)
|
|||||||
ADD_SUBDIRECTORY(osgmovie)
|
ADD_SUBDIRECTORY(osgmovie)
|
||||||
ADD_SUBDIRECTORY(osgmultitexture)
|
ADD_SUBDIRECTORY(osgmultitexture)
|
||||||
ADD_SUBDIRECTORY(osgoccluder)
|
ADD_SUBDIRECTORY(osgoccluder)
|
||||||
|
ADD_SUBDIRECTORY(osgocclusionquery)
|
||||||
ADD_SUBDIRECTORY(osgpagedlod)
|
ADD_SUBDIRECTORY(osgpagedlod)
|
||||||
ADD_SUBDIRECTORY(osgparametric)
|
ADD_SUBDIRECTORY(osgparametric)
|
||||||
ADD_SUBDIRECTORY(osgparticle)
|
ADD_SUBDIRECTORY(osgparticle)
|
||||||
|
7
examples/osgocclusionquery/CMakeLists.txt
Normal file
7
examples/osgocclusionquery/CMakeLists.txt
Normal file
@ -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)
|
850
examples/osgocclusionquery/osgocclusionquery.cpp
Normal file
850
examples/osgocclusionquery/osgocclusionquery.cpp
Normal file
@ -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 <osg/NodeVisitor>
|
||||||
|
#include <osg/Geode>
|
||||||
|
#include <osg/Geometry>
|
||||||
|
#include <osg/StateSet>
|
||||||
|
#include <osg/StateAttribute>
|
||||||
|
#include <osg/PolygonMode>
|
||||||
|
#include <osg/ColorMask>
|
||||||
|
#include <osg/PolygonOffset>
|
||||||
|
#include <osg/Depth>
|
||||||
|
|
||||||
|
#include <osgDB/ReadFile>
|
||||||
|
#include <osgDB/WriteFile>
|
||||||
|
|
||||||
|
#include <osgUtil/Optimizer>
|
||||||
|
|
||||||
|
#include <osgViewer/Viewer>
|
||||||
|
#include <osgViewer/ViewerEventHandlers>
|
||||||
|
|
||||||
|
#include <osgGA/StateSetManipulator>
|
||||||
|
|
||||||
|
#include <osg/OcclusionQueryNode>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
|
// 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<osg::StateSet> _state;
|
||||||
|
osg::ref_ptr<osg::StateSet> _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<osg::Geometry *>(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<osg::Group*>( node.getParent( np ) );
|
||||||
|
if (parent != NULL)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::OcclusionQueryNode> 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<osg::OcclusionQueryNode> oqnPtr = &oqn;
|
||||||
|
|
||||||
|
unsigned int np = oqn.getNumParents();
|
||||||
|
while (np--)
|
||||||
|
{
|
||||||
|
osg::Group* parent = dynamic_cast<osg::Group*>( 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<osg::Node>
|
||||||
|
createBox()
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Geode> 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<osg::Geometry> geom = new osg::Geometry;
|
||||||
|
osg::ref_ptr<osg::Vec3Array> 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<osg::Vec4Array> 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<osg::Vec3Array> 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<osg::Node>
|
||||||
|
createRandomTriangles( unsigned int num )
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Geode> 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<osg::Geometry> geom = new osg::Geometry;
|
||||||
|
// Disable display lists to decrease performance.
|
||||||
|
geom->setUseDisplayList( false );
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec3Array> 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<num; i++)
|
||||||
|
{
|
||||||
|
osg::Vec3& v0 = (*v)[ i*3+0 ];
|
||||||
|
osg::Vec3& v1 = (*v)[ i*3+1 ];
|
||||||
|
osg::Vec3& v2 = (*v)[ i*3+2 ];
|
||||||
|
v0 = osg::Vec3( RAND_NEG1_TO_1, RAND_NEG1_TO_1, RAND_NEG1_TO_1 );
|
||||||
|
v1 = osg::Vec3( RAND_NEG1_TO_1, RAND_NEG1_TO_1, RAND_NEG1_TO_1 );
|
||||||
|
v2 = osg::Vec3( RAND_NEG1_TO_1, RAND_NEG1_TO_1, RAND_NEG1_TO_1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec4Array> 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; i<num; i++)
|
||||||
|
{
|
||||||
|
osg::Vec4& c0 = (*c)[ i ];
|
||||||
|
c0 = osg::Vec4( RAND_0_TO_1, RAND_0_TO_1, RAND_0_TO_1, 1. );
|
||||||
|
}
|
||||||
|
|
||||||
|
geom->addPrimitiveSet( 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<osg::Node>
|
||||||
|
createStockScene()
|
||||||
|
{
|
||||||
|
// Create a simple box occluder
|
||||||
|
osg::ref_ptr<osg::Group> 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<osg::OcclusionQueryNode> 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<osg::Node> 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();
|
||||||
|
}
|
@ -30,6 +30,7 @@ class LightSource;
|
|||||||
class LOD;
|
class LOD;
|
||||||
class MatrixTransform;
|
class MatrixTransform;
|
||||||
class OccluderNode;
|
class OccluderNode;
|
||||||
|
class OcclusionQueryNode;
|
||||||
class PagedLOD;
|
class PagedLOD;
|
||||||
class PositionAttitudeTransform;
|
class PositionAttitudeTransform;
|
||||||
class Projection;
|
class Projection;
|
||||||
@ -256,6 +257,7 @@ class OSG_EXPORT NodeVisitor : public virtual Referenced
|
|||||||
virtual void apply(PagedLOD& node) { apply((LOD&)node); }
|
virtual void apply(PagedLOD& node) { apply((LOD&)node); }
|
||||||
virtual void apply(ClearNode& node) { apply((Group&)node); }
|
virtual void apply(ClearNode& node) { apply((Group&)node); }
|
||||||
virtual void apply(OccluderNode& 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.*/
|
/** Callback for managing database paging, such as generated by PagedLOD nodes.*/
|
||||||
|
113
include/osg/OcclusionQueryNode
Normal file
113
include/osg/OcclusionQueryNode
Normal file
@ -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 <osg/Export>
|
||||||
|
#include <osg/CopyOp>
|
||||||
|
#include <osg/Group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
@ -91,6 +91,7 @@ class OSGUTIL_EXPORT CullVisitor : public osg::NodeVisitor, public osg::CullStac
|
|||||||
virtual void apply(osg::ClearNode& node);
|
virtual void apply(osg::ClearNode& node);
|
||||||
virtual void apply(osg::Camera& node);
|
virtual void apply(osg::Camera& node);
|
||||||
virtual void apply(osg::OccluderNode& node);
|
virtual void apply(osg::OccluderNode& node);
|
||||||
|
virtual void apply(osg::OcclusionQueryNode& node);
|
||||||
|
|
||||||
/** Push state set on the current state group.
|
/** Push state set on the current state group.
|
||||||
* If the state exists in a child state group of the current
|
* If the state exists in a child state group of the current
|
||||||
|
@ -91,6 +91,7 @@ SET(LIB_PUBLIC_HEADERS
|
|||||||
${HEADER_PATH}/Notify
|
${HEADER_PATH}/Notify
|
||||||
${HEADER_PATH}/Object
|
${HEADER_PATH}/Object
|
||||||
${HEADER_PATH}/OccluderNode
|
${HEADER_PATH}/OccluderNode
|
||||||
|
${HEADER_PATH}/OcclusionQueryNode
|
||||||
${HEADER_PATH}/OperationThread
|
${HEADER_PATH}/OperationThread
|
||||||
${HEADER_PATH}/PagedLOD
|
${HEADER_PATH}/PagedLOD
|
||||||
${HEADER_PATH}/Plane
|
${HEADER_PATH}/Plane
|
||||||
@ -247,6 +248,7 @@ ADD_LIBRARY(${LIB_NAME}
|
|||||||
Notify.cpp
|
Notify.cpp
|
||||||
Object.cpp
|
Object.cpp
|
||||||
OccluderNode.cpp
|
OccluderNode.cpp
|
||||||
|
OcclusionQueryNode.cpp
|
||||||
OperationThread.cpp
|
OperationThread.cpp
|
||||||
PagedLOD.cpp
|
PagedLOD.cpp
|
||||||
Point.cpp
|
Point.cpp
|
||||||
|
686
src/osg/OcclusionQueryNode.cpp
Normal file
686
src/osg/OcclusionQueryNode.cpp
Normal file
@ -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 <osg/OcclusionQueryNode>
|
||||||
|
#include <OpenThreads/ScopedLock>
|
||||||
|
#include <osg/Timer>
|
||||||
|
#include <osg/Notify>
|
||||||
|
#include <osg/CopyOp>
|
||||||
|
#include <osg/Vec3>
|
||||||
|
#include <osg/MatrixTransform>
|
||||||
|
#include <osg/Group>
|
||||||
|
#include <osg/Geode>
|
||||||
|
#include <osg/Geometry>
|
||||||
|
#include <osg/BoundingBox>
|
||||||
|
#include <osg/BoundingSphere>
|
||||||
|
#include <osg/Referenced>
|
||||||
|
#include <osg/ComputeBoundsVisitor>
|
||||||
|
#include <osg/StateSet>
|
||||||
|
#include <osg/StateAttribute>
|
||||||
|
#include <osg/PolygonMode>
|
||||||
|
#include <osg/ColorMask>
|
||||||
|
#include <osg/PolygonOffset>
|
||||||
|
#include <osg/Depth>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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<TestResult*> 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<RetrieveQueriesCallback*>( 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<TestResult*>( *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<OpenThreads::Mutex> 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<OpenThreads::Mutex> 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<unsigned int>( clampMe );
|
||||||
|
if ( OptionLoader::instance()->getOption( "QueryFrameCount", clampMe ) )
|
||||||
|
_queryFrameCount = (clampMe < 1) ? 1 : static_cast<unsigned int>( 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<osgUtil::CullVisitor*>( &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<OpenThreads::Mutex> 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<OpenThreads::Mutex> 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<OpenThreads::Mutex> 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<osg::OcclusionQueryNode*>( this );
|
||||||
|
|
||||||
|
|
||||||
|
osg::ComputeBoundsVisitor cbv;
|
||||||
|
nonConstThis->accept( cbv );
|
||||||
|
osg::BoundingBox bb = cbv.getBoundingBox();
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec3Array> 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<osg::Geometry> geom = new osg::Geometry;
|
||||||
|
geom->setDataVariance( osg::Object::DYNAMIC );
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec4Array> 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() );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -42,6 +42,7 @@ Node.cpp
|
|||||||
NodeCallback.cpp
|
NodeCallback.cpp
|
||||||
Object.cpp
|
Object.cpp
|
||||||
OccluderNode.cpp
|
OccluderNode.cpp
|
||||||
|
OcclusionQueryNode.cpp
|
||||||
PagedLOD.cpp
|
PagedLOD.cpp
|
||||||
Point.cpp
|
Point.cpp
|
||||||
PointSprite.cpp
|
PointSprite.cpp
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include <osg/ClipNode>
|
#include <osg/ClipNode>
|
||||||
#include <osg/TexGenNode>
|
#include <osg/TexGenNode>
|
||||||
#include <osg/OccluderNode>
|
#include <osg/OccluderNode>
|
||||||
|
#include <osg/OcclusionQueryNode>
|
||||||
#include <osg/Notify>
|
#include <osg/Notify>
|
||||||
#include <osg/TexEnv>
|
#include <osg/TexEnv>
|
||||||
#include <osg/AlphaFunc>
|
#include <osg/AlphaFunc>
|
||||||
@ -1392,5 +1393,34 @@ void CullVisitor::apply(osg::OccluderNode& node)
|
|||||||
popOccludersCurrentMask(_nodePath);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user