From 4c5a1885d2a7b64eb7cc6c1768b000a123d5fe5b Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Tue, 25 Nov 2014 10:58:23 +0000 Subject: [PATCH] From PawelKsiezopolski, "This submission contains a new example for OSG : a geometry instancing rendering algorithm consisting of two consequent phases : MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - first phase is a GLSL shader performing object culling and LOD picking ( a culling shader ). Every culled object is represented as GL_POINT in the input osg::Geometry. The output of the culling shader is a set of object LODs that need to be rendered. The output is stored in texture buffer objects. No pixel is drawn to the screen because GL_RASTERIZER_DISCARD mode is used. - second phase draws osg::Geometry containing merged LODs using glDrawArraysIndirect() function. Information about quantity of instances to render, its positions and other parameters is sourced from texture buffer objects filled in the first phase. The example uses various OpenGL 4.2 features such as texture buffer objects, atomic counters, image units and functions defined in GL_ARB_shader_image_load_store extension to achieve its goal and thus will not work on graphic cards with older OpenGL versions. The example was tested on Linux and Windows with NVidia 570 and 580 cards. The tests on AMD cards were not conducted ( due to lack of it ). The tests were performed using OSG revision 14088. The main advantages of this rendering method : - instanced rendering capable of drawing thousands of different objects with almost no CPU intervention ( cull and draw times are close to 0 ms ). - input objects may be sourced from any OSG graph ( for example - information about object points may be stored in a PagedLOD graph. This way we may cover the whole countries with trees, buildings and other objects ). Furthermore if we create osgDB plugins that generate data on the fly, we may generate information for every grass blade for that country. - every object may have its own parameters and thus may be distinct from other objects of the same type. - relatively low memory footprint ( single object information is stored in a few vertex attributes ). - no GPU->CPU roundtrip typical for such methods ( method uses atomic counters and glDrawArraysIndirect() function instead of OpenGL queries. This way information about quantity of rendered objects never goes back to CPU. The typical GPU->CPU roundtrip cost is about 2 ms ). - this example also shows how to render dynamic objects ( objects that may change its position ) with moving parts ( like car wheels or airplane propellers ) . The obvious extension to that dynamic method would be the animated crowd rendering. - rendered objects may be easily replaced ( there is no need to process the whole OSG graphs, because these graphs store only positional information ). The main disadvantages of a method : - the maximum quantity of objects to render must be known beforehand ( because texture buffer objects holding data between phases have constant size ). - OSG statistics are flawed ( they don't know anymore how many objects are drawn ). - osgUtil::Intersection does not work Example application may be used to make some performance tests, so below you will find some extended parameter description : --skip-dynamic - skip rendering of dynamic objects if you only want to observe static object statistics --skip-static - the same for static objects --dynamic-area-size - size of the area for dynamic rendering. Default = 1000 meters ( square 1000m x 1000m ). Along with density defines how many dynamic objects is there in the example. --static-area-size - the same for static objects. Default = 2000 meters ( square 2000m x 2000m ). Example application defines some parameters (density, LOD ranges, object's triangle count). You may manipulate its values using below described modifiers: --density-modifier - density modifier in percent. Default = 100%. Density ( along with LOD ranges ) defines maximum quantity of rendered objects. registerType() function accepts maximum density ( in objects per square kilometer ) as its parameter. --lod-modifier - defines the LOD ranges. Default = 100%. --triangle-modifier - defines the number of triangles in finally rendered objects. Default = 100 %. --instances-per-cell - for static rendering the application builds OSG graph using InstanceCell class ( this class is a modified version of Cell class from osgforest example - it builds simple quadtree from a list of static instances ). This parameter defines maximum number of instances in a single osg::Group in quadtree. If, for example, you modify it to value=100, you will see really big cull time in OSG statistics ( because resulting tree generated by InstanceCell will be very deep ). Default value = 4096 . --export-objects - write object geometries and quadtree of instances to osgt files for later analysis. --use-multi-draw - use glMultiDrawArraysIndirect() instead of glDrawArraysIndirect() in a draw shader. Thanks to this we may render all ( different ) objects using only one draw call. Requires OpenGL version 4.3 and some more work from me, because now it does not work ( probably I implemented it wrong, or Windows NVidia driver has errors, because it hangs the apllication at the moment ). This application is inspired by Daniel Rákos work : "GPU based dynamic geometry LOD" that may be found under this address : http://rastergrid.com/blog/2010/10/gpu-based-dynamic-geometry-lod/ There are however some differences : - Daniel Rákos uses GL queries to count objects to render, while this example uses atomic counters ( no GPU->CPU roundtrip ) - this example does not use transform feedback buffers to store intermediate data ( it uses texture buffer objects instead ). - I use only the vertex shader to cull objects, whereas Daniel Rákos uses vertex shader and geometry shader ( because only geometry shader can send more than one primitive to transform feedback buffers ). - objects in the example are drawn using glDrawArraysIndirect() function, instead of glDrawElementsInstanced(). Finally there are some things to consider/discuss : - the whole algorithm exploits nice OpenGL feature that any GL buffer may be bound as any type of buffer ( in our example a buffer is once bound as a texture buffer object, and later is bound as GL_DRAW_INDIRECT_BUFFER ). osg::TextureBuffer class has one handy method to do that trick ( bindBufferAs() ), and new primitive sets use osg::TextureBuffer as input. For now I added new primitive sets to example ( DrawArraysIndirect and MultiDrawArraysIndirect defined in examples/osggpucull/DrawIndirectPrimitiveSet.h ), but if Robert will accept its current implementations ( I mean - primitive sets that have osg::TextureBuffer in constructor ), I may add it to osg/include/PrimitiveSet header. - I used BufferTemplate class writen and published by Aurelien in submission forum some time ago. For some reason this class never got into osg/include, but is really needed during creation of UBOs, TBOs, and possibly SSBOs in the future. I added std::vector specialization to that template class. - I needed to create similar osg::Geometries with variable number of vertices ( to create different LODs in my example ). For this reason I've written some code allowing me to create osg::Geometries from osg::Shape descendants. This code may be found in ShapeToGeometry.* files. Examples of use are in osggpucull.cpp . The question is : should this code stay in example, or should it be moved to osgUtil ? - this remark is important for NVidia cards on Linux and Windows : if you have "Sync to VBlank" turned ON in nvidia-settings and you want to see real GPU times in OSG statistics window, you must set the power management settings to "Prefer maximum performance", because when "Adaptive mode" is used, the graphic card's clock may be slowed down by the driver during program execution ( On Linux when OpenGL application starts in adaptive mode, clock should work as fast as possible, but after one minute of program execution, the clock slows down ). This happens when GPU time in OSG statistics window is shorter than 3 ms. " git-svn-id: http://svn.openscenegraph.org/osg/OpenSceneGraph/trunk@14531 16af8721-9629-0410-8352-f15c8da7e697 --- examples/CMakeLists.txt | 2 +- .../osggpucull/AggregateGeometryVisitor.cpp | 84 + .../osggpucull/AggregateGeometryVisitor.h | 290 ++++ examples/osggpucull/CMakeLists.txt | 16 + .../osggpucull/DrawIndirectPrimitiveSet.cpp | 139 ++ .../osggpucull/DrawIndirectPrimitiveSet.h | 100 ++ examples/osggpucull/GpuCullShaders.h | 592 +++++++ examples/osggpucull/ShapeToGeometry.cpp | 1173 +++++++++++++ examples/osggpucull/ShapeToGeometry.h | 109 ++ examples/osggpucull/osggpucull.cpp | 1536 +++++++++++++++++ include/osg/BufferTemplate | 104 ++ src/osg/CMakeLists.txt | 3 +- 12 files changed, 4146 insertions(+), 2 deletions(-) create mode 100644 examples/osggpucull/AggregateGeometryVisitor.cpp create mode 100644 examples/osggpucull/AggregateGeometryVisitor.h create mode 100644 examples/osggpucull/CMakeLists.txt create mode 100644 examples/osggpucull/DrawIndirectPrimitiveSet.cpp create mode 100644 examples/osggpucull/DrawIndirectPrimitiveSet.h create mode 100644 examples/osggpucull/GpuCullShaders.h create mode 100644 examples/osggpucull/ShapeToGeometry.cpp create mode 100644 examples/osggpucull/ShapeToGeometry.h create mode 100644 examples/osggpucull/osggpucull.cpp create mode 100644 include/osg/BufferTemplate diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 488663829..eec6791cb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -64,6 +64,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osglightpoint) ADD_SUBDIRECTORY(osglogicop) ADD_SUBDIRECTORY(osglogo) + ADD_SUBDIRECTORY(osggpucull) ADD_SUBDIRECTORY(osggpx) ADD_SUBDIRECTORY(osggraphicscost) ADD_SUBDIRECTORY(osgmanipulator) @@ -174,7 +175,6 @@ IF(DYNAMIC_OPENSCENEGRAPH) IF(NOT OSG_GLES1_AVAILABLE AND NOT OSG_GLES2_AVAILABLE AND NOT OSG_GL3_AVAILABLE) ADD_SUBDIRECTORY(osgscreencapture) - ADD_SUBDIRECTORY(osgframerenderer) ADD_SUBDIRECTORY(osgmotionblur) ADD_SUBDIRECTORY(osgteapot) ENDIF() diff --git a/examples/osggpucull/AggregateGeometryVisitor.cpp b/examples/osggpucull/AggregateGeometryVisitor.cpp new file mode 100644 index 000000000..ae85f041a --- /dev/null +++ b/examples/osggpucull/AggregateGeometryVisitor.cpp @@ -0,0 +1,84 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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 "AggregateGeometryVisitor.h" + +AggregateGeometryVisitor::AggregateGeometryVisitor( ConvertTrianglesOperator* ctOperator ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) +{ + _ctOperator.setConverter(ctOperator); + init(); +} + +void AggregateGeometryVisitor::init() +{ + _aggregatedGeometry = new osg::Geometry; + _ctOperator.initGeometry( _aggregatedGeometry.get() ); + _matrixStack.clear(); +} + +AggregateGeometryVisitor::AddObjectResult AggregateGeometryVisitor::addObject( osg::Node* object, unsigned int typeID, unsigned int lodNumber ) +{ + unsigned int currentVertexFirst = _aggregatedGeometry->getVertexArray()->getNumElements(); + _currentTypeID = typeID; + _currentLodNumber = lodNumber; + object->accept(*this); + unsigned int currentVertexCount = _aggregatedGeometry->getVertexArray()->getNumElements() - currentVertexFirst; + _aggregatedGeometry->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::TRIANGLES, currentVertexFirst, currentVertexCount ) ); + _matrixStack.clear(); + return AddObjectResult( currentVertexFirst, currentVertexCount, _aggregatedGeometry->getNumPrimitiveSets()-1 ); +} + +void AggregateGeometryVisitor::apply( osg::Node& node ) +{ + bool pushed = _ctOperator.pushNode(&node); + traverse(node); + if( pushed ) _ctOperator.popNode(); +} + +void AggregateGeometryVisitor::apply( osg::Transform& transform ) +{ + bool pushed = _ctOperator.pushNode(&transform); + osg::Matrix matrix; + if( !_matrixStack.empty() ) + matrix = _matrixStack.back(); + transform.computeLocalToWorldMatrix( matrix, this ); + _matrixStack.push_back( matrix ); + + traverse(transform); + + _matrixStack.pop_back(); + if( pushed ) _ctOperator.popNode(); +} + +void AggregateGeometryVisitor::apply( osg::Geode& geode ) +{ + bool pushed = _ctOperator.pushNode(&geode); + + osg::Matrix matrix; + if(! _matrixStack.empty() ) + matrix = _matrixStack.back(); + for( unsigned int i=0; iasGeometry(); + if ( geom != NULL ) + { + _ctOperator.setGeometryData( matrix, geom, _aggregatedGeometry.get(), (float) _currentTypeID, (float) _currentLodNumber ); + geom->accept( _ctOperator ); + } + } + + traverse(geode); + if( pushed ) _ctOperator.popNode(); +} diff --git a/examples/osggpucull/AggregateGeometryVisitor.h b/examples/osggpucull/AggregateGeometryVisitor.h new file mode 100644 index 000000000..f569e8e85 --- /dev/null +++ b/examples/osggpucull/AggregateGeometryVisitor.h @@ -0,0 +1,290 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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 AGREGATE_GEOMETRY_VISITOR_H +#define AGREGATE_GEOMETRY_VISITOR_H 1 +#include +#include +#include +#include +#include + +// AggregateGeometryVisitor uses ConvertTrianglesOperator to create and fill osg::Geometry +// with data matching users needs +struct ConvertTrianglesOperator : public osg::Referenced +{ + ConvertTrianglesOperator() + : osg::Referenced() + { + } + virtual void initGeometry( osg::Geometry* outputGeometry ) = 0; + virtual bool pushNode( osg::Node* node ) + { + return false; + } + virtual void popNode() = 0; + virtual void setGeometryData( const osg::Matrix &matrix, osg::Geometry *inputGeometry, osg::Geometry* outputGeometry, float typeID, float lodNumber ) = 0; + virtual void operator() ( unsigned int i1, unsigned int i2, unsigned int i3 ) = 0; +}; + +class GetVec2FromArrayVisitor : public osg::ValueVisitor +{ +public: + GetVec2FromArrayVisitor() + { + } + void apply( GLfloat& value ) + { + out = osg::Vec2( value, 0.0 ); + } + void apply( osg::Vec2& value ) + { + out = osg::Vec2( value.x(), value.y() ); + } + virtual void apply( osg::Vec2d& value ) + { + out = osg::Vec2( value.x(), value.y() ); + } + void apply( osg::Vec3& value ) + { + out = osg::Vec2( value.x(), value.y() ); + } + void apply( osg::Vec4& value ) + { + out = osg::Vec2( value.x(), value.y() ); + } + void apply( osg::Vec3d& value ) + { + out = osg::Vec2( value.x(), value.y() ); + } + void apply( osg::Vec4d& value ) + { + out = osg::Vec2( value.x(), value.y() ); + } + + osg::Vec2 out; +}; + + +// ConvertTrianglesOperatorClassic is a ConvertTrianglesOperator that creates +// aggregated geometry with standard set of vertex attributes : vertices, normals, color and texCoord0. +// texCoord1 holds additional information about vertex ( typeID, lodNumber, boneIndex ) +struct ConvertTrianglesOperatorClassic : public ConvertTrianglesOperator +{ + ConvertTrianglesOperatorClassic() + : ConvertTrianglesOperator(), _typeID(0.0f), _lodNumber(0.0f) + + { + _boneIndices.push_back(0.0); + } + virtual void initGeometry( osg::Geometry* outputGeometry ) + { + osg::Vec3Array* vertices = new osg::Vec3Array; outputGeometry->setVertexArray( vertices ); + osg::Vec4Array* colors = new osg::Vec4Array; outputGeometry->setColorArray( colors, osg::Array::BIND_PER_VERTEX ); + osg::Vec3Array* normals = new osg::Vec3Array; outputGeometry->setNormalArray( normals, osg::Array::BIND_PER_VERTEX ); + osg::Vec2Array* texCoords0 = new osg::Vec2Array; outputGeometry->setTexCoordArray( 0, texCoords0 ); + osg::Vec3Array* texCoords1 = new osg::Vec3Array; outputGeometry->setTexCoordArray( 1, texCoords1 ); + outputGeometry->setStateSet(NULL); + } + virtual bool pushNode( osg::Node* node ) + { + std::map::iterator it = _boneNames.find( node->getName() ); + if(it==_boneNames.end()) + return false; + _boneIndices.push_back( it->second ); + return true; + } + virtual void popNode() + { + _boneIndices.pop_back(); + } + virtual void setGeometryData( const osg::Matrix &matrix, osg::Geometry *inputGeometry, osg::Geometry* outputGeometry, float typeID, float lodNumber ) + { + _matrix = matrix; + + _inputVertices = dynamic_cast( inputGeometry->getVertexArray() ); + _inputColors = dynamic_cast( inputGeometry->getColorArray() ); + _inputNormals = dynamic_cast( inputGeometry->getNormalArray() ); + _inputTexCoord0 = inputGeometry->getTexCoordArray(0); + + _outputVertices = dynamic_cast( outputGeometry->getVertexArray() ); + _outputColors = dynamic_cast( outputGeometry->getColorArray() ); + _outputNormals = dynamic_cast( outputGeometry->getNormalArray() ); + _outputTexCoord0 = dynamic_cast( outputGeometry->getTexCoordArray(0) ); + _outputTexCoord1 = dynamic_cast( outputGeometry->getTexCoordArray(1) ); + + _typeID = typeID; + _lodNumber = lodNumber; + } + virtual void operator() ( unsigned int i1, unsigned int i2, unsigned int i3 ) + { + unsigned int ic1=i1, ic2=i2, ic3=i3, in1=i1, in2=i2, in3=i3, it01=i1, it02=i2, it03=i3; + if ( _inputColors!=NULL && _inputColors->size() == 1 ) + { + ic1=0; ic2=0; ic3=0; + } + if ( _inputNormals!=NULL && _inputNormals->size() == 1 ) + { + in1=0; in2=0; in3=0; + } + if ( _inputTexCoord0!=NULL && _inputTexCoord0->getNumElements()==1 ) + { + it01=0; it02=0; it03=0; + } + + _outputVertices->push_back( _inputVertices->at( i1 ) * _matrix ); + _outputVertices->push_back( _inputVertices->at( i2 ) * _matrix ); + _outputVertices->push_back( _inputVertices->at( i3 ) * _matrix ); + + if( _inputColors != NULL ) + { + _outputColors->push_back( _inputColors->at( ic1 ) ); + _outputColors->push_back( _inputColors->at( ic2 ) ); + _outputColors->push_back( _inputColors->at( ic3 ) ); + } + else + { + for(unsigned int i=0; i<3; ++i) + _outputColors->push_back( osg::Vec4(1.0,1.0,1.0,1.0) ); + } + + if( _inputNormals != NULL ) + { + _outputNormals->push_back( osg::Matrix::transform3x3( _inputNormals->at( in1 ), _matrix ) ); + _outputNormals->push_back( osg::Matrix::transform3x3( _inputNormals->at( in2 ), _matrix ) ); + _outputNormals->push_back( osg::Matrix::transform3x3( _inputNormals->at( in3 ), _matrix ) ); + } + else + { + for(unsigned int i=0; i<3; ++i) + _outputNormals->push_back( osg::Vec3( 0.0,0.0,1.0 ) ); + } + if( _inputTexCoord0 != NULL ) + { + _inputTexCoord0->accept( it01, _inputTexCoord0Visitor ); + _outputTexCoord0->push_back( _inputTexCoord0Visitor.out ); + _inputTexCoord0->accept( it02, _inputTexCoord0Visitor ); + _outputTexCoord0->push_back( _inputTexCoord0Visitor.out ); + _inputTexCoord0->accept( it03, _inputTexCoord0Visitor ); + _outputTexCoord0->push_back( _inputTexCoord0Visitor.out ); + } + else + { + for(unsigned int i=0; i<3; ++i) + _outputTexCoord0->push_back( osg::Vec2(0.0,0.0) ); + } + + for(unsigned int i=0; i<3; ++i) + _outputTexCoord1->push_back( osg::Vec3( _typeID, _lodNumber, _boneIndices.back() ) ); + } + void registerBoneByName( const std::string& boneName, int boneIndex ) + { + _boneNames[boneName] = float(boneIndex); + } + + osg::Matrix _matrix; + + osg::Vec3Array* _inputVertices; + osg::Vec4Array* _inputColors; + osg::Vec3Array* _inputNormals; + osg::Array* _inputTexCoord0; + + osg::Vec3Array* _outputVertices; + osg::Vec4Array* _outputColors; + osg::Vec3Array* _outputNormals; + osg::Vec2Array* _outputTexCoord0; + osg::Vec3Array* _outputTexCoord1; + + float _typeID; + float _lodNumber; + std::vector _boneIndices; + + std::map _boneNames; + + GetVec2FromArrayVisitor _inputTexCoord0Visitor; +}; + + + +class AggregateGeometryVisitor : public osg::NodeVisitor +{ +public: + AggregateGeometryVisitor( ConvertTrianglesOperator* ctOperator ); + void init(); + + // osg::TriangleIndexFunctor uses its template parameter as a base class, so we must use an adapter pattern to hack it + struct ConvertTrianglesBridge + { + inline void setConverter( ConvertTrianglesOperator* cto ) + { + _converter = cto; + } + inline void initGeometry( osg::Geometry* outputGeometry ) + { + _converter->initGeometry(outputGeometry); + } + inline bool pushNode( osg::Node* node ) + { + return _converter->pushNode( node ); + } + inline void popNode() + { + _converter->popNode(); + } + inline void setGeometryData( const osg::Matrix &matrix, osg::Geometry *inputGeometry, osg::Geometry* outputGeometry, float typeID, float lodNumber ) + { + _converter->setGeometryData(matrix,inputGeometry,outputGeometry,typeID, lodNumber); + } + inline void operator() ( unsigned int i1, unsigned int i2, unsigned int i3 ) + { + _converter->operator()(i1,i2,i3); + } + + osg::ref_ptr _converter; + }; + + + // struct returning information about added object ( first vertex, vertex count, primitiveset index ) + // used later to create indirect command texture buffers + struct AddObjectResult + { + AddObjectResult( unsigned int f, unsigned int c, unsigned int i ) + : first(f), count(c), index(i) + { + } + unsigned int first; + unsigned int count; + unsigned int index; + }; + AddObjectResult addObject( osg::Node* object, unsigned int typeID, unsigned int lodNumber ); + + void apply( osg::Node& node ); + void apply( osg::Transform& transform ); + void apply( osg::Geode& geode ); + + inline osg::Geometry* getAggregatedGeometry() + { + return _aggregatedGeometry.get(); + } +protected: + osg::ref_ptr _aggregatedGeometry; + osg::TriangleIndexFunctor _ctOperator; + std::vector _matrixStack; + + unsigned int _currentTypeID; + unsigned int _currentLodNumber; +}; + +#endif \ No newline at end of file diff --git a/examples/osggpucull/CMakeLists.txt b/examples/osggpucull/CMakeLists.txt new file mode 100644 index 000000000..11d3b0463 --- /dev/null +++ b/examples/osggpucull/CMakeLists.txt @@ -0,0 +1,16 @@ +SET(TARGET_SRC + ShapeToGeometry.cpp + DrawIndirectPrimitiveSet.cpp + AggregateGeometryVisitor.cpp + osggpucull.cpp +) + +SET(TARGET_H + ShapeToGeometry.h + DrawIndirectPrimitiveSet.h + AggregateGeometryVisitor.h + GpuCullShaders.h +) + +#### end var setup ### +SETUP_EXAMPLE(osggpucull) diff --git a/examples/osggpucull/DrawIndirectPrimitiveSet.cpp b/examples/osggpucull/DrawIndirectPrimitiveSet.cpp new file mode 100644 index 000000000..36e06586e --- /dev/null +++ b/examples/osggpucull/DrawIndirectPrimitiveSet.cpp @@ -0,0 +1,139 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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 "DrawIndirectPrimitiveSet.h" +#include +#include +#include +#include +#include + +void DrawArraysIndirect::draw(osg::State& state, bool useVertexBufferObjects) const +{ + if( !_buffer.valid() ) + return; + _buffer->bindBufferAs( state.getContextID(), GL_DRAW_INDIRECT_BUFFER ); + +// if you want to see how many primitives were rendered - uncomment code below, but +// be warned : it is a serious performance killer ( because of GPU->CPU roundtrip ) + +// osg::Drawable::Extensions *dext = osg::Drawable::getExtensions( state.getContextID(),true ); +// int* tab = (int*)dext->glMapBuffer(GL_DRAW_INDIRECT_BUFFER,GL_READ_ONLY); +// int val = _indirect/sizeof(int); +// OSG_WARN<<"DrawArraysIndirect ("<glUnmapBuffer(GL_DRAW_INDIRECT_BUFFER); + + DrawIndirectGLExtensions *ext = DrawIndirectGLExtensions::getExtensions( state.getContextID(),true ); + ext->glDrawArraysIndirect( _mode, reinterpret_cast(_indirect) ); + _buffer->unbindBufferAs( state.getContextID(), GL_DRAW_INDIRECT_BUFFER ); +} + +void MultiDrawArraysIndirect::draw(osg::State& state, bool useVertexBufferObjects) const +{ + if( !_buffer.valid() ) + return; + _buffer->bindBufferAs( state.getContextID(), GL_DRAW_INDIRECT_BUFFER ); + + DrawIndirectGLExtensions *ext = DrawIndirectGLExtensions::getExtensions( state.getContextID(),true ); + ext->glMultiDrawArraysIndirect( _mode, reinterpret_cast(_indirect), _drawcount, _stride ); + _buffer->unbindBufferAs( state.getContextID(), GL_DRAW_INDIRECT_BUFFER ); +} + +DrawIndirectGLExtensions::DrawIndirectGLExtensions( unsigned int contextID ) +{ + setupGLExtensions( contextID ); +} + +DrawIndirectGLExtensions::DrawIndirectGLExtensions( const DrawIndirectGLExtensions &rhs ) + : Referenced() +{ + _glDrawArraysIndirect = rhs._glDrawArraysIndirect; + _glMultiDrawArraysIndirect = rhs._glMultiDrawArraysIndirect; + _glMemoryBarrier = rhs._glMemoryBarrier; +} + + +void DrawIndirectGLExtensions::lowestCommonDenominator( const DrawIndirectGLExtensions &rhs ) +{ + if ( !rhs._glDrawArraysIndirect ) + { + _glDrawArraysIndirect = rhs._glDrawArraysIndirect; + } + if ( !rhs._glMultiDrawArraysIndirect ) + { + _glMultiDrawArraysIndirect = rhs._glMultiDrawArraysIndirect; + } + if ( !rhs._glMemoryBarrier ) + { + _glMemoryBarrier = rhs._glMemoryBarrier; + } +} + +void DrawIndirectGLExtensions::setupGLExtensions( unsigned int contextID ) +{ + _glDrawArraysIndirect = 0; + _glMultiDrawArraysIndirect = 0; + osg::setGLExtensionFuncPtr( _glDrawArraysIndirect, "glDrawArraysIndirect" ); + osg::setGLExtensionFuncPtr( _glMultiDrawArraysIndirect, "glMultiDrawArraysIndirect" ); + osg::setGLExtensionFuncPtr( _glMemoryBarrier, "glMemoryBarrier" ); +} + +void DrawIndirectGLExtensions::glDrawArraysIndirect(GLenum mode, const void * indirect) const +{ + if ( _glDrawArraysIndirect ) + { + _glDrawArraysIndirect( mode, indirect ); + } + else + { + OSG_WARN<<"Error: glDrawArraysIndirect not supported by OpenGL driver"< > BufferedDrawIndirectGLExtensions; +static BufferedDrawIndirectGLExtensions bdiExtensions; + +DrawIndirectGLExtensions* DrawIndirectGLExtensions::getExtensions( unsigned int contextID,bool createIfNotInitalized ) +{ + if ( !bdiExtensions[contextID] && createIfNotInitalized ) + { + bdiExtensions[contextID] = new DrawIndirectGLExtensions( contextID ); + } + return bdiExtensions[contextID].get(); +} diff --git a/examples/osggpucull/DrawIndirectPrimitiveSet.h b/examples/osggpucull/DrawIndirectPrimitiveSet.h new file mode 100644 index 000000000..905f0ee71 --- /dev/null +++ b/examples/osggpucull/DrawIndirectPrimitiveSet.h @@ -0,0 +1,100 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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_DRAWINDIRECTPRIMITIVESET +#define OSG_DRAWINDIRECTPRIMITIVESET 1 + +#include +#include +#include + +#ifndef GL_ARB_draw_indirect + #define GL_DRAW_INDIRECT_BUFFER 0x8F3F + #define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#endif + +#ifndef GL_SHADER_IMAGE_ACCESS_BARRIER_BIT + #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 +#endif + + +class DrawArraysIndirect : public osg::DrawArrays +{ +public: + DrawArraysIndirect(GLenum mode=0, osg::TextureBuffer* buffer=NULL, unsigned int indirect=0) + : osg::DrawArrays(mode), _buffer(buffer), _indirect(indirect) + { + } + virtual osg::Object* cloneType() const { return new DrawArraysIndirect(); } + virtual osg::Object* clone(const osg::CopyOp& copyop) const { return NULL; } + virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast(obj)!=NULL; } + virtual const char* libraryName() const { return "osg"; } + virtual const char* className() const { return "DrawArraysIndirect"; } + + virtual void draw(osg::State& state, bool useVertexBufferObjects) const; +protected: + osg::ref_ptr _buffer; + unsigned int _indirect; +}; + +class MultiDrawArraysIndirect : public osg::DrawArrays +{ +public: + MultiDrawArraysIndirect(GLenum mode=0, osg::TextureBuffer* buffer=NULL, unsigned int indirect=0, GLsizei drawcount=0, GLsizei stride=0) + : osg::DrawArrays(mode), _buffer(buffer), _indirect(indirect), _drawcount(drawcount), _stride(stride) + { + } + virtual osg::Object* cloneType() const { return new MultiDrawArraysIndirect(); } + virtual osg::Object* clone(const osg::CopyOp& copyop) const { return NULL; } + virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast(obj)!=NULL; } + virtual const char* libraryName() const { return "osg"; } + virtual const char* className() const { return "MultiDrawArraysIndirect"; } + + virtual void draw(osg::State& state, bool useVertexBufferObjects) const; +protected: + osg::ref_ptr _buffer; + unsigned int _indirect; + GLsizei _drawcount; + GLsizei _stride; +}; + +class DrawIndirectGLExtensions : public osg::Referenced +{ +public: + DrawIndirectGLExtensions( unsigned int contextID ); + DrawIndirectGLExtensions( const DrawIndirectGLExtensions &rhs ); + void lowestCommonDenominator( const DrawIndirectGLExtensions &rhs ); + void setupGLExtensions( unsigned int contextID ); + + void glDrawArraysIndirect(GLenum mode, const void * indirect) const; + void glMultiDrawArraysIndirect(GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); + void glMemoryBarrier(GLbitfield barriers); + + static DrawIndirectGLExtensions* getExtensions( unsigned int contextID,bool createIfNotInitalized ); +protected: + + typedef void ( GL_APIENTRY *DrawArraysIndirectProc ) (GLenum mode, const void * indirect); + typedef void ( GL_APIENTRY *MultiDrawArraysIndirectProc ) (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); + typedef void ( GL_APIENTRY *MemoryBarrierProc ) (GLbitfield barriers); + + DrawArraysIndirectProc _glDrawArraysIndirect; + MultiDrawArraysIndirectProc _glMultiDrawArraysIndirect; + MemoryBarrierProc _glMemoryBarrier; + +}; + + + +#endif diff --git a/examples/osggpucull/GpuCullShaders.h b/examples/osggpucull/GpuCullShaders.h new file mode 100644 index 000000000..9eb7d8749 --- /dev/null +++ b/examples/osggpucull/GpuCullShaders.h @@ -0,0 +1,592 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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 GPU_CULL_SHADERS +#define GPU_CULL_SHADERS 1 + +char SHADER_STATIC_CULL_VERTEX[] = + "#version 420 compatibility\n" + "\n" + "uniform mat4 osg_ViewMatrixInverse;\n" + "\n" + "layout(R32I) coherent uniform iimageBuffer indirectCommand0;\n" + "layout(R32I) coherent uniform iimageBuffer indirectCommand1;\n" + "uniform int indirectCommandSize; // = sizeof(DrawArraysIndirectCommand) / sizeof(unsigned int) = 4\n" + "\n" + "layout(R32I) coherent uniform iimageBuffer getIndirectCommand( int index )\n" + "{\n" + " if(index==0) return indirectCommand0;\n" + " if(index==1) return indirectCommand1;\n" + " return indirectCommand0;\n" + "}\n" + "\n" + "layout(RGBA32F) coherent uniform imageBuffer indirectTarget0;\n" + "layout(RGBA32F) coherent uniform imageBuffer indirectTarget1;\n" + "\n" + "layout(RGBA32F) coherent uniform imageBuffer getIndirectTarget( int index )\n" + "{\n" + " if(index==0) return indirectTarget0;\n" + " if(index==1) return indirectTarget1;\n" + " return indirectTarget0;\n" + "}\n" + "\n" + "struct InstanceLOD\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 indirectTargetParams; // x=targetID, y=indexInTarget, z=offsetInTarget, w=lodMaxQuantity\n" + " vec4 distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance\n" + "};\n" + "\n" + "const int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8;\n" + "\n" + "struct InstanceType\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 params;\n" + " InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER];\n" + "};\n" + "\n" + "layout(std140) uniform instanceTypesData\n" + "{\n" + " InstanceType instanceTypes[32];\n" + "};\n" + "\n" + "// StaticInstance params are stored in vertex attributes\n" + "layout(location = 0) in vec3 VertexPosition;\n" + "layout(location = 10) in vec4 M0;\n" + "layout(location = 11) in vec4 M1;\n" + "layout(location = 12) in vec4 M2;\n" + "layout(location = 13) in vec4 M3;\n" + "layout(location = 14) in vec4 ExtraParams;\n" + "layout(location = 15) in vec4 IdParams;\n" + "\n" + "out vec4 fColor;\n" + "\n" + "bool boundingBoxInViewFrustum( in mat4 matrix, in vec3 bbMin, in vec3 bbMax )\n" + "{\n" + " vec4 BoundingBox[8];\n" + " BoundingBox[0] = matrix * vec4( bbMax.x, bbMax.y, bbMax.z, 1.0);\n" + " BoundingBox[1] = matrix * vec4( bbMin.x, bbMax.y, bbMax.z, 1.0);\n" + " BoundingBox[2] = matrix * vec4( bbMax.x, bbMin.y, bbMax.z, 1.0);\n" + " BoundingBox[3] = matrix * vec4( bbMin.x, bbMin.y, bbMax.z, 1.0);\n" + " BoundingBox[4] = matrix * vec4( bbMax.x, bbMax.y, bbMin.z, 1.0);\n" + " BoundingBox[5] = matrix * vec4( bbMin.x, bbMax.y, bbMin.z, 1.0);\n" + " BoundingBox[6] = matrix * vec4( bbMax.x, bbMin.y, bbMin.z, 1.0);\n" + " BoundingBox[7] = matrix * vec4( bbMin.x, bbMin.y, bbMin.z, 1.0);\n" + "\n" + " int outOfBound[6] = int[6]( 0, 0, 0, 0, 0, 0 );\n" + " for (int i=0; i<8; i++)\n" + " {\n" + " outOfBound[0] += int( BoundingBox[i].x > BoundingBox[i].w );\n" + " outOfBound[1] += int( BoundingBox[i].x < -BoundingBox[i].w );\n" + " outOfBound[2] += int( BoundingBox[i].y > BoundingBox[i].w );\n" + " outOfBound[3] += int( BoundingBox[i].y < -BoundingBox[i].w );\n" + " outOfBound[4] += int( BoundingBox[i].z > BoundingBox[i].w );\n" + " outOfBound[5] += int( BoundingBox[i].z < -BoundingBox[i].w );\n" + " }\n" + " return (outOfBound[0] < 8 ) && ( outOfBound[1] < 8 ) && ( outOfBound[2] < 8 ) && ( outOfBound[3] < 8 ) && ( outOfBound[4] < 8 ) && ( outOfBound[5] < 8 );\n" + "}\n" + "\n" + "void main(void) \n" + "{\n" + " mat4 instanceMatrix = mat4(M0,M1,M2,M3);\n" + " mat4 mvpoMatrix = gl_ModelViewProjectionMatrix * instanceMatrix;\n" + "\n" + " // gl_Position is created only for debugging purposes\n" + " gl_Position = mvpoMatrix * vec4(0.0,0.0,0.0,1.0);\n" + "\n" + " int instanceTypeIndex = int(IdParams.x);\n" + "\n" + " fColor = vec4(0.0,0.0,0.0,1.0);\n" + " if( boundingBoxInViewFrustum( mvpoMatrix, instanceTypes[instanceTypeIndex].bbMin.xyz, instanceTypes[instanceTypeIndex].bbMax.xyz ) )\n" + " {\n" + " fColor = vec4(1.0,0.0,0.0,1.0);\n" + " float distanceToObject = distance(osg_ViewMatrixInverse[3].xyz / osg_ViewMatrixInverse[3].w, instanceMatrix[3].xyz / instanceMatrix[3].w );\n" + " for(int i=0;i= instanceTypes[instanceTypeIndex].lods[i].distances.x ) && ( distanceToObject < instanceTypes[instanceTypeIndex].lods[i].distances.w ) )\n" + " {\n" + " fColor = vec4(1.0,1.0,0.0,1.0);\n" + " float fadeAlpha = 4.0 * ( clamp( (distanceToObject-instanceTypes[instanceTypeIndex].lods[i].distances.x)/( instanceTypes[instanceTypeIndex].lods[i].distances.y - instanceTypes[instanceTypeIndex].lods[i].distances.x), 0.0, 1.0 ) \n" + " + clamp( (distanceToObject- instanceTypes[instanceTypeIndex].lods[i].distances.z)/( instanceTypes[instanceTypeIndex].lods[i].distances.w - instanceTypes[instanceTypeIndex].lods[i].distances.z), 0.0, 1.0 ) );\n" + " int sampleMask = ( 0xF0 >> int(fadeAlpha) ) & 0xF;\n" + " int indirectCommandIndex = instanceTypes[instanceTypeIndex].lods[i].indirectTargetParams.x;\n" + " int indirectCommandAddress = instanceTypes[instanceTypeIndex].lods[i].indirectTargetParams.y;\n" + " int objectIndex = imageAtomicAdd( getIndirectCommand( indirectCommandIndex ), indirectCommandAddress*indirectCommandSize+1, 1 );\n" + " int indirectTargetAddress = 6*(instanceTypes[instanceTypeIndex].lods[i].indirectTargetParams.z + objectIndex);\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress+0, M0 );\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress+1, M1 );\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress+2, M2 );\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress+3, M3 );\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress+4, ExtraParams );\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress+5, vec4(IdParams.x,IdParams.y,float(sampleMask),0.0) );\n" + " }\n" + " }\n" + " }\n" + "}\n"; + + +char SHADER_STATIC_CULL_FRAGMENT[] = + "#version 420 compatibility\n" + "\n" + "layout(location = 0, index = 0) out vec4 FragmentColor;\n" + "\n" + "in vec4 fColor;\n" + "\n" + "void main()\n" + "{\n" + " FragmentColor = fColor;\n" + "}\n"; + + +char SHADER_STATIC_DRAW_0_VERTEX[] = + "#version 420 compatibility\n" + "\n" + "layout(location = 0) in vec4 VertexPosition;\n" + "layout(location = 2) in vec3 VertexNormal;\n" + "layout(location = 3) in vec4 VertexColor;\n" + "layout(location = 8) in vec2 VertexTexCoord0;\n" + "layout(location = 9) in vec3 VertexTexCoord1;\n" + "\n" + "struct InstanceLOD\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 indirectTargetParams; // x=targetID, y=indexInTarget, z=offsetInTarget, w=lodMaxQuantity\n" + " vec4 distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance\n" + "};\n" + "\n" + "const int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8;\n" + "\n" + "struct InstanceType\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 params;\n" + " InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER];\n" + "};\n" + "\n" + "layout(std140) uniform instanceTypesData\n" + "{\n" + " InstanceType instanceTypes[32];\n" + "};\n" + "\n" + "layout(RGBA32F) coherent uniform imageBuffer indirectTarget;\n" + "\n" + "out vec3 ecPosition3;\n" + "out vec3 ecNormal;\n" + "out vec2 TexCoord;\n" + "out vec4 color;\n" + "flat out int sMask;\n" + " \n" + "void main()\n" + "{\n" + " // every vertex has its type coded on VertexTexCoord1.x,\n" + " // and its lodNumber coded in VertexTexCoord1.y\n" + " int instanceTypeIndex = int(VertexTexCoord1.x);\n" + " int instanceLodNumber = int(VertexTexCoord1.y);\n" + " int indirectTargetAddress = 6*(instanceTypes[instanceTypeIndex].lods[instanceLodNumber].indirectTargetParams.z + gl_InstanceID);\n" + " mat4 instanceMatrix = mat4(\n" + " imageLoad(indirectTarget, indirectTargetAddress + 0),\n" + " imageLoad(indirectTarget, indirectTargetAddress + 1),\n" + " imageLoad(indirectTarget, indirectTargetAddress + 2),\n" + " imageLoad(indirectTarget, indirectTargetAddress + 3) );\n" + " vec4 extraParams = imageLoad(indirectTarget, indirectTargetAddress + 4);\n" + " vec4 idParams = imageLoad(indirectTarget, indirectTargetAddress + 5);\n" + "\n" + " sMask = int(idParams.z);\n" + "\n" + " mat4 mvMatrix = gl_ModelViewMatrix * instanceMatrix;\n" + " \n" + " // Do fixed functionality vertex transform\n" + " vec4 ecPos = mvMatrix * vec4(VertexPosition.xyz,1.0);\n" + " gl_Position = gl_ProjectionMatrix * ecPos;\n" + "\n" + " ecPosition3 = ecPos.xyz / ecPos.w;\n" + " ecNormal = normalize( mat3(mvMatrix) * VertexNormal.xyz );\n" + " \n" + " TexCoord = VertexTexCoord0.xy;\n" + " color.xyz = VertexColor.xyz * extraParams.x;\n" + " color.a = VertexColor.a;\n" + "}\n"; + + +char SHADER_STATIC_DRAW_0_FRAGMENT[] = + "#version 420 compatibility\n" + "\n" + "in vec3 ecPosition3;\n" + "in vec3 ecNormal;\n" + "in vec2 TexCoord;\n" + "in vec4 color;\n" + "\n" + "flat in int sMask;\n" + "\n" + "layout(location = 0) out vec4 FragColor;\n" + "\n" + "void main()\n" + "{\n" + "// simple fragment shader that calculates ambient + diffuse lighting with osg::LightSource\n" + " vec3 lightDir = normalize(vec3(gl_LightSource[0].position));\n" + " vec3 normal = normalize(ecNormal);\n" + " float NdotL = max(dot(normal, lightDir), 0.0);\n" + " FragColor = NdotL * color * gl_LightSource[0].diffuse + color * gl_LightSource[0].ambient;\n" + " gl_SampleMask[0] = sMask;\n" + "}\n"; + + +char SHADER_STATIC_DRAW_1_VERTEX[] = + "#version 420 compatibility\n" + "\n" + "layout(location = 0) in vec4 VertexPosition;\n" + "layout(location = 2) in vec3 VertexNormal;\n" + "layout(location = 3) in vec4 VertexColor;\n" + "layout(location = 8) in vec2 VertexTexCoord0;\n" + "layout(location = 9) in vec3 VertexTexCoord1;\n" + "\n" + "struct InstanceLOD\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 indirectTargetParams; // x=targetID, y=indexInTarget, z=offsetInTarget, w=lodMaxQuantity\n" + " vec4 distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance\n" + "};\n" + "\n" + "const int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8;\n" + "\n" + "struct InstanceType\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 params;\n" + " InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER];\n" + "};\n" + "\n" + "layout(std140) uniform instanceTypesData\n" + "{\n" + " InstanceType instanceTypes[32];\n" + "};\n" + "\n" + "layout(RGBA32F) coherent uniform imageBuffer indirectTarget;\n" + "\n" + "uniform vec2 windDirection;\n" + "uniform float osg_FrameTime;\n" + "\n" + "out vec3 ecPosition3;\n" + "out vec3 ecNormal;\n" + "out vec2 TexCoord;\n" + "out vec4 color;\n" + "flat out int sMask;\n" + " \n" + "void main()\n" + "{\n" + " // every vertex has its type coded on VertexTexCoord1.x,\n" + " // and its lodNumber coded in VertexTexCoord1.y\n" + " int instanceTypeIndex = int(VertexTexCoord1.x);\n" + " int instanceLodNumber = int(VertexTexCoord1.y);\n" + " int indirectTargetAddress = 6*(instanceTypes[instanceTypeIndex].lods[instanceLodNumber].indirectTargetParams.z + gl_InstanceID);\n" + " mat4 instanceMatrix = mat4(\n" + " imageLoad(indirectTarget, indirectTargetAddress + 0),\n" + " imageLoad(indirectTarget, indirectTargetAddress + 1),\n" + " imageLoad(indirectTarget, indirectTargetAddress + 2),\n" + " imageLoad(indirectTarget, indirectTargetAddress + 3) );\n" + " vec4 extraParams = imageLoad(indirectTarget, indirectTargetAddress + 4);\n" + " vec4 idParams = imageLoad(indirectTarget, indirectTargetAddress + 5);\n" + "\n" + " sMask = int(idParams.z);\n" + "\n" + " // simple tree waving in the wind. Amplitude, frequency and offset are coded in extraParams\n" + " vec4 mPos = instanceMatrix * vec4(VertexPosition.xyz,1.0);\n" + " float wavingAmplitute = VertexPosition.z * extraParams.y;\n" + " mPos.xy += windDirection * wavingAmplitute * sin( extraParams.z*osg_FrameTime + extraParams.w );\n" + "\n" + " mat4 mvMatrix = gl_ModelViewMatrix * instanceMatrix;\n" + " \n" + " // Do fixed functionality vertex transform\n" + " vec4 ecPos = osg_ModelViewMatrix * mPos;\n" + " gl_Position = gl_ProjectionMatrix * ecPos;\n" + "\n" + " ecPosition3 = ecPos.xyz / ecPos.w;\n" + " ecNormal = normalize( mat3(mvMatrix) * VertexNormal.xyz );\n" + " \n" + " TexCoord = VertexTexCoord0.xy;\n" + " color.xyz = VertexColor.xyz * extraParams.x;\n" + " color.a = VertexColor.a;\n" + "}\n"; + + +char SHADER_STATIC_DRAW_1_FRAGMENT[] = + "#version 420 compatibility\n" + "\n" + "in vec3 ecPosition3;\n" + "in vec3 ecNormal;\n" + "in vec2 TexCoord;\n" + "in vec4 color;\n" + "\n" + "flat in int sMask;\n" + "\n" + "layout(location = 0) out vec4 FragColor;\n" + "\n" + "void main()\n" + "{\n" + "// simple fragment shader that calculates ambient + diffuse lighting with osg::LightSource\n" + " vec3 lightDir = normalize(vec3(gl_LightSource[0].position));\n" + " vec3 normal = normalize(ecNormal);\n" + " float NdotL = max(dot(normal, lightDir), 0.0);\n" + " FragColor = NdotL * color * gl_LightSource[0].diffuse + color * gl_LightSource[0].ambient;\n" + " gl_SampleMask[0] = sMask;\n" + "}\n"; + + +char SHADER_DYNAMIC_CULL_VERTEX[] = + "#version 420 compatibility\n" + "\n" + "uniform mat4 osg_ViewMatrixInverse;\n" + "\n" + "uniform samplerBuffer dynamicInstancesData;\n" + "uniform int dynamicInstancesDataSize; // = sizeof(DynamicInstance) / sizeof(osg::Vec4f)\n" + "\n" + "layout(R32I) coherent uniform iimageBuffer indirectCommand0;\n" + "uniform int indirectCommandSize; // = sizeof(DrawArraysIndirectCommand) / sizeof(unsigned int) = 4\n" + "\n" + "layout(R32I) coherent uniform iimageBuffer getIndirectCommand( int index )\n" + "{\n" + " if(index==0) return indirectCommand0;\n" + "}\n" + "\n" + "layout(RGBA32I) coherent uniform iimageBuffer indirectTarget0;\n" + "\n" + "layout(RGBA32I) coherent uniform iimageBuffer getIndirectTarget( int index )\n" + "{\n" + " if(index==0) return indirectTarget0;\n" + "}\n" + "\n" + "struct InstanceLOD\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 indirectTargetParams; // x=targetID, y=indexInTarget, z=offsetInTarget, w=lodMaxQuantity\n" + " vec4 distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance\n" + "};\n" + "\n" + "const int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8;\n" + "\n" + "struct InstanceType\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 params;\n" + " InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER];\n" + "};\n" + "\n" + "layout(std140) uniform instanceTypesData\n" + "{\n" + " InstanceType instanceTypes[32];\n" + "};\n" + "\n" + "layout(location = 0) in vec3 VertexPosition; // xyz - used only for debugging purposes\n" + "layout(location = 10) in vec4 InstanceID; // typeid, id - points to the data in dynamicInstancesData buffer\n" + "\n" + "bool boundingBoxInViewFrustum( in mat4 matrix, in vec3 bbMin, in vec3 bbMax )\n" + "{\n" + " vec4 BoundingBox[8];\n" + " BoundingBox[0] = matrix * vec4( bbMax.x, bbMax.y, bbMax.z, 1.0);\n" + " BoundingBox[1] = matrix * vec4( bbMin.x, bbMax.y, bbMax.z, 1.0);\n" + " BoundingBox[2] = matrix * vec4( bbMax.x, bbMin.y, bbMax.z, 1.0);\n" + " BoundingBox[3] = matrix * vec4( bbMin.x, bbMin.y, bbMax.z, 1.0);\n" + " BoundingBox[4] = matrix * vec4( bbMax.x, bbMax.y, bbMin.z, 1.0);\n" + " BoundingBox[5] = matrix * vec4( bbMin.x, bbMax.y, bbMin.z, 1.0);\n" + " BoundingBox[6] = matrix * vec4( bbMax.x, bbMin.y, bbMin.z, 1.0);\n" + " BoundingBox[7] = matrix * vec4( bbMin.x, bbMin.y, bbMin.z, 1.0);\n" + "\n" + " int outOfBound[6] = int[6]( 0, 0, 0, 0, 0, 0 );\n" + " for (int i=0; i<8; i++)\n" + " {\n" + " outOfBound[0] += int( BoundingBox[i].x > BoundingBox[i].w );\n" + " outOfBound[1] += int( BoundingBox[i].x < -BoundingBox[i].w );\n" + " outOfBound[2] += int( BoundingBox[i].y > BoundingBox[i].w );\n" + " outOfBound[3] += int( BoundingBox[i].y < -BoundingBox[i].w );\n" + " outOfBound[4] += int( BoundingBox[i].z > BoundingBox[i].w );\n" + " outOfBound[5] += int( BoundingBox[i].z < -BoundingBox[i].w );\n" + " }\n" + " return (outOfBound[0] < 8 ) && ( outOfBound[1] < 8 ) && ( outOfBound[2] < 8 ) && ( outOfBound[3] < 8 ) && ( outOfBound[4] < 8 ) && ( outOfBound[5] < 8 );\n" + "}\n" + "\n" + "void main(void) \n" + "{\n" + " // get object matrices\n" + " int instanceIndex = int(InstanceID.y);\n" + " int instanceAddress = instanceIndex * dynamicInstancesDataSize;\n" + " mat4 instanceMatrix = mat4 (\n" + " texelFetch(dynamicInstancesData, instanceAddress),\n" + " texelFetch(dynamicInstancesData, instanceAddress + 1),\n" + " texelFetch(dynamicInstancesData, instanceAddress + 2),\n" + " texelFetch(dynamicInstancesData, instanceAddress + 3) );\n" + " mat4 mvpoMatrix = gl_ModelViewProjectionMatrix * instanceMatrix;\n" + "\n" + " // gl_Position is created only for debugging purposes\n" + " gl_Position = mvpoMatrix * vec4(0.0,0.0,0.0,1.0);\n" + "\n" + " int instanceTypeIndex = int(InstanceID.x);\n" + "\n" + " if( boundingBoxInViewFrustum( mvpoMatrix, instanceTypes[instanceTypeIndex].bbMin.xyz, instanceTypes[instanceTypeIndex].bbMax.xyz ) )\n" + " {\n" + " float distanceToObject = distance(osg_ViewMatrixInverse[3].xyz / osg_ViewMatrixInverse[3].w, instanceMatrix[3].xyz / instanceMatrix[3].w );\n" + " for(int i=0;i= instanceTypes[instanceTypeIndex].lods[i].distances.x ) && ( distanceToObject < instanceTypes[instanceTypeIndex].lods[i].distances.w ) )\n" + " {\n" + " float fadeAlpha = 4.0 * ( clamp( (distanceToObject-instanceTypes[instanceTypeIndex].lods[i].distances.x)/( instanceTypes[instanceTypeIndex].lods[i].distances.y - instanceTypes[instanceTypeIndex].lods[i].distances.x), 0.0, 1.0 ) \n" + " + clamp( (distanceToObject- instanceTypes[instanceTypeIndex].lods[i].distances.z)/( instanceTypes[instanceTypeIndex].lods[i].distances.w - instanceTypes[instanceTypeIndex].lods[i].distances.z), 0.0, 1.0 ) );\n" + " int sampleMask = ( 0xF0 >> int(fadeAlpha) ) & 0xF;\n" + " int indirectCommandIndex = instanceTypes[instanceTypeIndex].lods[i].indirectTargetParams.x;\n" + " int indirectCommandAddress = instanceTypes[instanceTypeIndex].lods[i].indirectTargetParams.y;\n" + " int objectIndex = imageAtomicAdd( getIndirectCommand( indirectCommandIndex ), indirectCommandAddress*indirectCommandSize+1, 1 );\n" + " int indirectTargetAddress = instanceTypes[instanceTypeIndex].lods[i].indirectTargetParams.z + objectIndex;\n" + " ivec4 indirectCommandData = ivec4( instanceTypeIndex, instanceIndex, sampleMask, 0 );\n" + " imageStore( getIndirectTarget(indirectCommandIndex), indirectTargetAddress, indirectCommandData );\n" + " }\n" + " }\n" + " }\n" + "}\n"; + + +char SHADER_DYNAMIC_CULL_FRAGMENT[] = + "#version 420 compatibility\n" + "\n" + "layout(location = 0, index = 0) out vec4 FragmentColor;\n" + "\n" + "void main()\n" + "{\n" + " FragmentColor = vec4(1.0,1.0,0.1,1.0);\n" + "}\n"; + + +char SHADER_DYNAMIC_DRAW_0_VERTEX[] = + "#version 420 compatibility\n" + "\n" + "uniform samplerBuffer dynamicInstancesData;\n" + "uniform int dynamicInstancesDataSize; // = sizeof(DynamicInstance) / sizeof(osg::Vec4f)\n" + "\n" + "layout(location = 0) in vec4 VertexPosition;\n" + "layout(location = 2) in vec3 VertexNormal;\n" + "layout(location = 3) in vec4 VertexColor;\n" + "layout(location = 8) in vec2 VertexTexCoord0;\n" + "layout(location = 9) in vec3 VertexTexCoord1;\n" + "\n" + "struct InstanceLOD\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 indirectTargetParams; // x=targetID, y=indexInTarget, z=offsetInTarget, w=lodMaxQuantity\n" + " vec4 distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance\n" + "};\n" + "\n" + "const int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8;\n" + "\n" + "struct InstanceType\n" + "{\n" + " vec4 bbMin;\n" + " vec4 bbMax;\n" + " ivec4 params;\n" + " InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER];\n" + "};\n" + "\n" + "layout(std140) uniform instanceTypesData\n" + "{\n" + " InstanceType instanceTypes[32];\n" + "};\n" + "\n" + "layout(RGBA32I) coherent uniform iimageBuffer indirectTarget;\n" + "\n" + "out vec3 ecPosition3;\n" + "out vec3 ecNormal;\n" + "out vec2 TexCoord;\n" + "out vec4 color;\n" + "flat out int sMask;\n" + " \n" + "void main()\n" + "{\n" + " // every vertex has its type coded on VertexTexCoord1.x,\n" + " // its lodNumber coded in VertexTexCoord1.y\n" + " // and bone index coded in VertexTexCoord1.z\n" + " int instanceTypeIndex = int(VertexTexCoord1.x);\n" + " int instanceLodNumber = int(VertexTexCoord1.y);\n" + " int boneIndex = int(VertexTexCoord1.z);\n" + " int indirectTargetAddress = instanceTypes[instanceTypeIndex].lods[instanceLodNumber].indirectTargetParams.z + gl_InstanceID;\n" + "\n" + " // if we know instance type, lod number and gl_InstanceID, we can reconstruct all instance data\n" + " ivec4 indirectCommandData = imageLoad(indirectTarget, indirectTargetAddress);\n" + " // now its time to read instance matrix and other additional params\n" + " int instanceAddress = indirectCommandData.y * dynamicInstancesDataSize;\n" + " mat4 instanceMatrix = mat4 (\n" + " texelFetch(dynamicInstancesData, instanceAddress),\n" + " texelFetch(dynamicInstancesData, instanceAddress + 1),\n" + " texelFetch(dynamicInstancesData, instanceAddress + 2),\n" + " texelFetch(dynamicInstancesData, instanceAddress + 3) );\n" + "\n" + " vec4 instanceExtraParams = texelFetch(dynamicInstancesData, instanceAddress + 4);\n" + " // we also need boneMatrix to animate an object\n" + " int boneAddress = instanceAddress + 6 + 4*boneIndex;\n" + " mat4 boneMatrix = mat4 (\n" + " texelFetch(dynamicInstancesData, boneAddress),\n" + " texelFetch(dynamicInstancesData, boneAddress + 1),\n" + " texelFetch(dynamicInstancesData, boneAddress + 2),\n" + " texelFetch(dynamicInstancesData, boneAddress + 3) );\n" + "\n" + " sMask = indirectCommandData.z;\n" + "\n" + " mat4 mvMatrix = gl_ModelViewMatrix * instanceMatrix * boneMatrix;\n" + " \n" + " // Do fixed functionality vertex transform\n" + " vec4 ecPos = mvMatrix * vec4(VertexPosition.xyz,1.0);\n" + " gl_Position = gl_ProjectionMatrix * ecPos;\n" + "\n" + " ecPosition3 = ecPos.xyz / ecPos.w;\n" + " ecNormal = normalize( mat3(mvMatrix) * VertexNormal.xyz );\n" + " \n" + " TexCoord = VertexTexCoord0.xy;\n" + " color.xyz = VertexColor.xyz * instanceExtraParams.x;\n" + " color.a = VertexColor.a;\n" + "}\n"; + + +char SHADER_DYNAMIC_DRAW_0_FRAGMENT[] = + "#version 420 compatibility\n" + "\n" + "in vec3 ecPosition3;\n" + "in vec3 ecNormal;\n" + "in vec2 TexCoord;\n" + "in vec4 color;\n" + "\n" + "flat in int sMask;\n" + "\n" + "layout(location = 0) out vec4 FragColor;\n" + "\n" + "void main()\n" + "{\n" + "// simple fragment shader that calculates ambient + diffuse lighting with osg::LightSource\n" + " vec3 lightDir = normalize(vec3(gl_LightSource[0].position));\n" + " vec3 normal = normalize(ecNormal);\n" + " float NdotL = max(dot(normal, lightDir), 0.0);\n" + " FragColor = NdotL * color * gl_LightSource[0].diffuse + color * gl_LightSource[0].ambient;\n" + " gl_SampleMask[0] = sMask;\n" + "}\n"; + + +#endif + diff --git a/examples/osggpucull/ShapeToGeometry.cpp b/examples/osggpucull/ShapeToGeometry.cpp new file mode 100644 index 000000000..346f222d7 --- /dev/null +++ b/examples/osggpucull/ShapeToGeometry.cpp @@ -0,0 +1,1173 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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 "ShapeToGeometry.h" +#include + + +FakeGLBeginEndAdapter::FakeGLBeginEndAdapter() + : osg::GLBeginEndAdapter(NULL) +{ + geometry = new osg::Geometry; +} + +void FakeGLBeginEndAdapter::PushMatrix() +{ + if (_matrixStack.empty()) + _matrixStack.push_back(osg::Matrixd()); + else + _matrixStack.push_back(_matrixStack.back()); +} +void FakeGLBeginEndAdapter::MultMatrixd(const GLdouble* m) +{ + if (_matrixStack.empty()) + _matrixStack.push_back(osg::Matrixd()); + _matrixStack.back().preMult(osg::Matrixd(m)); +} +void FakeGLBeginEndAdapter::Translated(GLdouble x, GLdouble y, GLdouble z) +{ + if (_matrixStack.empty()) + _matrixStack.push_back(osg::Matrixd()); + _matrixStack.back().preMultTranslate(osg::Vec3d(x,y,z)); +} + +void FakeGLBeginEndAdapter::Scaled(GLdouble x, GLdouble y, GLdouble z) +{ + if (_matrixStack.empty()) + { + _matrixStack.push_back(osg::Matrixd()); + } + _matrixStack.back().preMultScale(osg::Vec3d(x,y,z)); +} + +void FakeGLBeginEndAdapter::Rotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + if (_matrixStack.empty()) + _matrixStack.push_back(osg::Matrixd()); + _matrixStack.back().preMultRotate(osg::Quat(osg::DegreesToRadians(angle), osg::Vec3d(x,y,z))); +} + +void FakeGLBeginEndAdapter::End() +{ + if (!_vertices || _vertices->empty()) return; + + if (!_matrixStack.empty()) + { + const osg::Matrixd& matrix = _matrixStack.back(); + osg::Matrixd inverse; + inverse.invert(matrix); + + for(osg::Vec3Array::iterator itr = _vertices->begin(); + itr != _vertices->end(); + ++itr) + { + *itr = *itr * matrix; + } + + if (_normalAssigned && _normals.valid()) + { + for(osg::Vec3Array::iterator itr = _normals->begin(); + itr != _normals->end(); + ++itr) + { + *itr = osg::Matrixd::transform3x3(inverse, *itr); + (*itr).normalize(); + } + } + else + { + _overallNormal = osg::Matrixd::transform3x3(inverse, _overallNormal); + _overallNormal.normalize(); + } + } + + + if (_colorAssigned) + { + if(geometry->getColorArray() == NULL ) + geometry->setColorArray( new osg::Vec4Array, osg::Array::BIND_PER_VERTEX ); + osg::Vec4Array* gColors = dynamic_cast(geometry->getColorArray()); + gColors->insert( gColors->end(), _colors->begin(), _colors->end() ); + } + else if (_overallColorAssigned) + { + if(geometry->getColorArray() == NULL ) + geometry->setColorArray( new osg::Vec4Array, osg::Array::BIND_PER_VERTEX ); + osg::Vec4Array* gColors=dynamic_cast(geometry->getColorArray()); + gColors->insert( gColors->end(), _vertices->size(), _overallColor ); + } + + if (_normalAssigned) + { + if(geometry->getNormalArray() == NULL ) + geometry->setNormalArray( new osg::Vec3Array, osg::Array::BIND_PER_VERTEX ); + osg::Vec3Array* gNormals = dynamic_cast(geometry->getNormalArray()); + gNormals->insert( gNormals->end(), _normals->begin(), _normals->end() ); + } + else if (_overallNormalAssigned) + { + if(geometry->getNormalArray() == NULL ) + geometry->setNormalArray( new osg::Vec3Array, osg::Array::BIND_PER_VERTEX ); + osg::Vec3Array* gNormals = dynamic_cast(geometry->getNormalArray()); + gNormals->insert( gNormals->end(), _vertices->size(), _overallNormal ); + } + + for(unsigned int unit=0; unit<_texCoordAssignedList.size(); ++unit) + { + if (_texCoordAssignedList[unit] && _texCoordsList[unit].valid()) + { + if(geometry->getTexCoordArray(unit) == NULL ) + geometry->setTexCoordArray( unit, new osg::Vec4Array, osg::Array::BIND_PER_VERTEX ); + osg::Vec4Array* gTexCoords = dynamic_cast(geometry->getTexCoordArray(unit)); + gTexCoords->insert( gTexCoords->end(), _texCoordsList[unit]->begin(), _texCoordsList[unit]->end() ); + } + } + + for(unsigned int unit=0; unit<_vertexAttribAssignedList.size(); ++unit) + { + if (_vertexAttribAssignedList[unit] && _vertexAttribsList[unit].valid()) + { + if(geometry->getVertexAttribArray(unit) == NULL ) + geometry->setVertexAttribArray( unit, new osg::Vec4Array, osg::Array::BIND_PER_VERTEX ); + osg::Vec4Array* gVertexAttribs = dynamic_cast(geometry->getVertexAttribArray(unit)); + gVertexAttribs->insert( gVertexAttribs->end(), _vertexAttribsList[unit]->begin(), _vertexAttribsList[unit]->end() ); + } + } + + if(geometry->getVertexArray() == NULL ) + geometry->setVertexArray( new osg::Vec3Array ); + osg::Vec3Array* gVertices = dynamic_cast(geometry->getVertexArray()); + unsigned int vOffset = gVertices->size(); + unsigned int vSize = _vertices->size(); + gVertices->insert( gVertices->end(), _vertices->begin(), _vertices->end() ); + + if (_primitiveMode==GL_QUAD_STRIP) // will the winding be wrong? Do we need to swap it? + geometry->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLE_STRIP, vOffset, vSize ) ); + else if (_primitiveMode==GL_POLYGON) + geometry->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLE_FAN, vOffset, vSize ) ); + else + geometry->addPrimitiveSet( new osg::DrawArrays( _primitiveMode, vOffset, vSize ) ); +} + +void ShapeToGeometryVisitor::drawCylinderBody(unsigned int numSegments, float radius, float height) +{ + const float angleDelta = 2.0f*osg::PI/(float)numSegments; + const float texCoordDelta = 1.0f/(float)numSegments; + + const float r = radius; + const float h = height; + + float basez = -h*0.5f; + float topz = h*0.5f; + + float angle = 0.0f; + float texCoord = 0.0f; + + bool drawFrontFace = _hints ? _hints->getCreateFrontFace() : true; + bool drawBackFace = _hints ? _hints->getCreateBackFace() : false; + + // The only difference between the font & back face loops is that the + // normals are inverted and the order of the vertex pairs is reversed. + // The code is mostly duplicated in order to hoist the back/front face + // test out of the loop for efficiency + + gl.Begin(GL_QUAD_STRIP); + + if (drawFrontFace) { + + for(unsigned int bodyi=0; + bodyigetCreateFrontFace() : true; + bool drawBackFace = _hints ? _hints->getCreateBackFace() : false; + + float angleDelta = osg::PI*2.0f/(float)numSegments; + float texCoordHorzDelta = 1.0f/(float)numSegments; + + float lBase=-osg::PI*0.5f + (top?(lDelta*(numRows/2)):0.0f); + float rBase=(top?(cosf(lBase)*radius):0.0f); + float zBase=(top?(sinf(lBase)*radius):-radius); + float vBase=(top?(vDelta*(numRows/2)):0.0f); + float nzBase=(top?(sinf(lBase)):-1.0f); + float nRatioBase=(top?(cosf(lBase)):0.0f); + + unsigned int rowbegin = top?numRows/2:0; + unsigned int rowend = top?numRows:numRows/2; + + for(unsigned int rowi=rowbegin; rowigetCreateFrontFace() : true; + bool drawBackFace = _hints ? _hints->getCreateBackFace() : false; + + unsigned int numSegments = 40; + unsigned int numRows = 20; + float ratio = (_hints ? _hints->getDetailRatio() : 1.0f); + if (ratio > 0.0f && ratio != 1.0f) { + numRows = (unsigned int) (numRows * ratio); + if (numRows < MIN_NUM_ROWS) + numRows = MIN_NUM_ROWS; + numSegments = (unsigned int) (numSegments * ratio); + if (numSegments < MIN_NUM_SEGMENTS) + numSegments = MIN_NUM_SEGMENTS; + } + + float lDelta = osg::PI/(float)numRows; + float vDelta = 1.0f/(float)numRows; + + float angleDelta = osg::PI*2.0f/(float)numSegments; + float texCoordHorzDelta = 1.0f/(float)numSegments; + + if (drawBackFace) + { + float lBase=-osg::PI*0.5f; + float rBase=0.0f; + float zBase=-sphere.getRadius(); + float vBase=0.0f; + float nzBase=-1.0f; + float nRatioBase=0.0f; + + for(unsigned int rowi=0; rowigetCreateBody() : true); + bool createTop = (_hints ? _hints->getCreateTop() : true); + bool createBottom = (_hints ? _hints->getCreateBottom() : true); + + float dx = box.getHalfLengths().x(); + float dy = box.getHalfLengths().y(); + float dz = box.getHalfLengths().z(); + + gl.PushMatrix(); + + gl.Translated(box.getCenter().x(),box.getCenter().y(),box.getCenter().z()); + + if (!box.zeroRotation()) + { + osg::Matrixd rotation(box.computeRotationMatrix()); + gl.MultMatrixd(rotation.ptr()); + } + + gl.Begin(GL_QUADS); + + if (createBody) { + // -ve y plane + gl.Normal3f(0.0f,-1.0f,0.0f); + + gl.TexCoord2f(0.0f,1.0f); + gl.Vertex3f(-dx,-dy,dz); + + gl.TexCoord2f(0.0f,0.0f); + gl.Vertex3f(-dx,-dy,-dz); + + gl.TexCoord2f(1.0f,0.0f); + gl.Vertex3f(dx,-dy,-dz); + + gl.TexCoord2f(1.0f,1.0f); + gl.Vertex3f(dx,-dy,dz); + + // +ve y plane + gl.Normal3f(0.0f,1.0f,0.0f); + + gl.TexCoord2f(0.0f,1.0f); + gl.Vertex3f(dx,dy,dz); + + gl.TexCoord2f(0.0f,0.0f); + gl.Vertex3f(dx,dy,-dz); + + gl.TexCoord2f(1.0f,0.0f); + gl.Vertex3f(-dx,dy,-dz); + + gl.TexCoord2f(1.0f,1.0f); + gl.Vertex3f(-dx,dy,dz); + + // +ve x plane + gl.Normal3f(1.0f,0.0f,0.0f); + + gl.TexCoord2f(0.0f,1.0f); + gl.Vertex3f(dx,-dy,dz); + + gl.TexCoord2f(0.0f,0.0f); + gl.Vertex3f(dx,-dy,-dz); + + gl.TexCoord2f(1.0f,0.0f); + gl.Vertex3f(dx,dy,-dz); + + gl.TexCoord2f(1.0f,1.0f); + gl.Vertex3f(dx,dy,dz); + + // -ve x plane + gl.Normal3f(-1.0f,0.0f,0.0f); + + gl.TexCoord2f(0.0f,1.0f); + gl.Vertex3f(-dx,dy,dz); + + gl.TexCoord2f(0.0f,0.0f); + gl.Vertex3f(-dx,dy,-dz); + + gl.TexCoord2f(1.0f,0.0f); + gl.Vertex3f(-dx,-dy,-dz); + + gl.TexCoord2f(1.0f,1.0f); + gl.Vertex3f(-dx,-dy,dz); + } + + if (createTop) { + // +ve z plane + gl.Normal3f(0.0f,0.0f,1.0f); + + gl.TexCoord2f(0.0f,1.0f); + gl.Vertex3f(-dx,dy,dz); + + gl.TexCoord2f(0.0f,0.0f); + gl.Vertex3f(-dx,-dy,dz); + + gl.TexCoord2f(1.0f,0.0f); + gl.Vertex3f(dx,-dy,dz); + + gl.TexCoord2f(1.0f,1.0f); + gl.Vertex3f(dx,dy,dz); + } + + if (createBottom) { + // -ve z plane + gl.Normal3f(0.0f,0.0f,-1.0f); + + gl.TexCoord2f(0.0f,1.0f); + gl.Vertex3f(dx,dy,-dz); + + gl.TexCoord2f(0.0f,0.0f); + gl.Vertex3f(dx,-dy,-dz); + + gl.TexCoord2f(1.0f,0.0f); + gl.Vertex3f(-dx,-dy,-dz); + + gl.TexCoord2f(1.0f,1.0f); + gl.Vertex3f(-dx,dy,-dz); + } + + gl.End(); + + gl.PopMatrix(); + +} + +void ShapeToGeometryVisitor::apply(const osg::Cone& cone) +{ + gl.PushMatrix(); + + gl.Translated(cone.getCenter().x(),cone.getCenter().y(),cone.getCenter().z()); + + if (!cone.zeroRotation()) + { + osg::Matrixd rotation(cone.computeRotationMatrix()); + gl.MultMatrixd(rotation.ptr()); + } + + // evaluate hints + bool createBody = (_hints ? _hints->getCreateBody() : true); + bool createBottom = (_hints ? _hints->getCreateBottom() : true); + + unsigned int numSegments = 40; + unsigned int numRows = 10; + float ratio = (_hints ? _hints->getDetailRatio() : 1.0f); + if (ratio > 0.0f && ratio != 1.0f) { + numRows = (unsigned int) (numRows * ratio); + if (numRows < MIN_NUM_ROWS) + numRows = MIN_NUM_ROWS; + numSegments = (unsigned int) (numSegments * ratio); + if (numSegments < MIN_NUM_SEGMENTS) + numSegments = MIN_NUM_SEGMENTS; + } + + float r = cone.getRadius(); + float h = cone.getHeight(); + + float normalz = r/(sqrtf(r*r+h*h)); + float normalRatio = 1.0f/(sqrtf(1.0f+normalz*normalz)); + normalz *= normalRatio; + + float angleDelta = 2.0f*osg::PI/(float)numSegments; + float texCoordHorzDelta = 1.0/(float)numSegments; + float texCoordRowDelta = 1.0/(float)numRows; + float hDelta = cone.getHeight()/(float)numRows; + float rDelta = cone.getRadius()/(float)numRows; + + float topz=cone.getHeight()+cone.getBaseOffset(); + float topr=0.0f; + float topv=1.0f; + float basez=topz-hDelta; + float baser=rDelta; + float basev=topv-texCoordRowDelta; + float angle; + float texCoord; + + if (createBody) { + for(unsigned int rowi=0; rowigetCreateBody() : true); + bool createTop = (_hints ? _hints->getCreateTop() : true); + bool createBottom = (_hints ? _hints->getCreateBottom() : true); + + unsigned int numSegments = 40; + float ratio = (_hints ? _hints->getDetailRatio() : 1.0f); + if (ratio > 0.0f && ratio != 1.0f) { + numSegments = (unsigned int) (numSegments * ratio); + if (numSegments < MIN_NUM_SEGMENTS) + numSegments = MIN_NUM_SEGMENTS; + } + + + // cylinder body + if (createBody) + drawCylinderBody(numSegments, cylinder.getRadius(), cylinder.getHeight()); + + float angleDelta = 2.0f*osg::PI/(float)numSegments; + float texCoordDelta = 1.0f/(float)numSegments; + + float r = cylinder.getRadius(); + float h = cylinder.getHeight(); + + float basez = -h*0.5f; + float topz = h*0.5f; + + float angle = 0.0f; + float texCoord = 0.0f; + + // cylinder top + if (createTop) { + gl.Begin(GL_TRIANGLE_FAN); + + gl.Normal3f(0.0f,0.0f,1.0f); + gl.TexCoord2f(0.5f,0.5f); + gl.Vertex3f(0.0f,0.0f,topz); + + angle = 0.0f; + texCoord = 0.0f; + for(unsigned int topi=0; + topigetCreateBody() : true); + bool createTop = (_hints ? _hints->getCreateTop() : true); + bool createBottom = (_hints ? _hints->getCreateBottom() : true); + + unsigned int numSegments = 40; + unsigned int numRows = 20; + float ratio = (_hints ? _hints->getDetailRatio() : 1.0f); + if (ratio > 0.0f && ratio != 1.0f) { + numSegments = (unsigned int) (numSegments * ratio); + if (numSegments < MIN_NUM_SEGMENTS) + numSegments = MIN_NUM_SEGMENTS; + numRows = (unsigned int) (numRows * ratio); + if (numRows < MIN_NUM_ROWS) + numRows = MIN_NUM_ROWS; + } + + // if numRows is odd the top and bottom halves of sphere won't match, so bump up to the next event numRows + if ((numRows%2)!=0) ++numRows; + + + // capsule cylindrical body + if (createBody) + drawCylinderBody(numSegments, capsule.getRadius(), capsule.getHeight()); + + // capsule top cap + if (createTop) + drawHalfSphere(numSegments, numRows, capsule.getRadius(), SphereTopHalf, capsule.getHeight()/2.0f); + + // capsule bottom cap + if (createBottom) + drawHalfSphere(numSegments, numRows, capsule.getRadius(), SphereBottomHalf, -capsule.getHeight()/2.0f); + + gl.PopMatrix(); +} + +void ShapeToGeometryVisitor::apply(const osg::InfinitePlane&) +{ + OSG_NOTICE<<"Warning: ShapeToGeometryVisitor::apply(const InfinitePlane& plane) not yet implemented. "<getNumElements();i+=3) + { + const osg::Vec3& v1=(*vertices)[indices->index(i)]; + const osg::Vec3& v2=(*vertices)[indices->index(i+1)]; + const osg::Vec3& v3=(*vertices)[indices->index(i+2)]; + osg::Vec3 normal = (v2-v1)^(v3-v2); + normal.normalize(); + + gl.Normal3fv(normal.ptr()); + gl.Vertex3fv(v1.ptr()); + gl.Vertex3fv(v2.ptr()); + gl.Vertex3fv(v3.ptr()); + + } + + gl.End(); + } +} + +void ShapeToGeometryVisitor::apply(const osg::ConvexHull& hull) +{ + apply((const osg::TriangleMesh&)hull); +} + +void ShapeToGeometryVisitor::apply(const osg::HeightField& field) +{ + if (field.getNumColumns()==0 || field.getNumRows()==0) return; + + gl.PushMatrix(); + + gl.Translated(field.getOrigin().x(),field.getOrigin().y(),field.getOrigin().z()); + + + if (!field.zeroRotation()) + { + osg::Matrixd rotation(field.computeRotationMatrix()); + gl.MultMatrixd(rotation.ptr()); + } + + float dx = field.getXInterval(); + float dy = field.getYInterval(); + + float du = 1.0f/((float)field.getNumColumns()-1.0f); + float dv = 1.0f/((float)field.getNumRows()-1.0f); + + float vBase = 0.0f; + + osg::Vec3 vertTop; + osg::Vec3 normTop; + + osg::Vec3 vertBase; + osg::Vec3 normBase; + + if (field.getSkirtHeight()!=0.0f) + { + gl.Begin(GL_QUAD_STRIP); + + float u = 0.0f; + + // draw bottom skirt + unsigned int col; + vertTop.y() = 0.0f; + for(col=0;colaccept(*this); + } +} + +osg::Geometry* convertShapeToGeometry(const osg::Shape& shape, const osg::TessellationHints* hints) +{ + osg::ref_ptr geometry; + { + ShapeToGeometryVisitor gfsVisitor(hints); + shape.accept( gfsVisitor ); + geometry = gfsVisitor.getGeometry(); + } + return geometry.release(); +} + +osg::Geometry* convertShapeToGeometry(const osg::Shape& shape, const osg::TessellationHints* hints, const osg::Vec4& color) +{ + osg::ref_ptr geometry = convertShapeToGeometry(shape,hints); + osg::Vec4Array* colorArray = new osg::Vec4Array; + colorArray->insert( colorArray->end(), geometry->getVertexArray()->getNumElements(), color ); + geometry->setColorArray( colorArray, osg::Array::BIND_PER_VERTEX ); + return geometry.release(); +} + + +osg::Geode* convertShapeToGeode(const osg::Shape& shape, const osg::TessellationHints* hints) +{ + osg::Geode *geode = new osg::Geode; + geode->addDrawable( convertShapeToGeometry(shape,hints) ); + return geode; +} + +osg::Geode* convertShapeToGeode(const osg::Shape& shape, const osg::TessellationHints* hints, const osg::Vec4& color) +{ + osg::Geode *geode = new osg::Geode; + geode->addDrawable( convertShapeToGeometry(shape,hints,color) ); + return geode; +} + + +void ShapesToGeometriesVisitor::apply( osg::Geode& geode ) +{ + for(unsigned int i=0; i( geode.getDrawable(i) ); + if(drawable==NULL) + continue; + osg::Geometry* newGeom = convertShapeToGeometry(*(drawable->getShape()), _hints); + newGeom->setStateSet( drawable->getStateSet() ); + geode.setDrawable( i, newGeom ); + } + traverse(geode); +} diff --git a/examples/osggpucull/ShapeToGeometry.h b/examples/osggpucull/ShapeToGeometry.h new file mode 100644 index 000000000..3c414e578 --- /dev/null +++ b/examples/osggpucull/ShapeToGeometry.h @@ -0,0 +1,109 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * 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 SHAPE_TO_GEOMETRY +#define SHAPE_TO_GEOMETRY 1 +#include +#include +#include +#include + +// arbitrary minima for rows & segments ( from shapedrawable.cpp ) +const unsigned int MIN_NUM_ROWS = 3; +const unsigned int MIN_NUM_SEGMENTS = 5; + +// osg::GLBeginEndAdapter descendant that stores data for osg::Geometry creation +class FakeGLBeginEndAdapter : public osg::GLBeginEndAdapter +{ +public: + FakeGLBeginEndAdapter(); + + void PushMatrix(); + void MultMatrixd(const GLdouble* m); + void Translated(GLdouble x, GLdouble y, GLdouble z); + void Scaled(GLdouble x, GLdouble y, GLdouble z); + void Rotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); + void End(); + + osg::ref_ptr geometry; +}; + +class ShapeToGeometryVisitor : public osg::ConstShapeVisitor +{ +public: + + ShapeToGeometryVisitor(const osg::TessellationHints* hints) + : osg::ConstShapeVisitor(), _hints(hints) + { + } + + virtual void apply(const osg::Sphere&); + virtual void apply(const osg::Box&); + virtual void apply(const osg::Cone&); + virtual void apply(const osg::Cylinder&); + virtual void apply(const osg::Capsule&); + virtual void apply(const osg::InfinitePlane&); + + virtual void apply(const osg::TriangleMesh&); + virtual void apply(const osg::ConvexHull&); + virtual void apply(const osg::HeightField&); + + virtual void apply(const osg::CompositeShape&); + + osg::Geometry* getGeometry() { return gl.geometry.get(); } + + + const osg::TessellationHints* _hints; + FakeGLBeginEndAdapter gl; +protected: + + ShapeToGeometryVisitor& operator = (const ShapeToGeometryVisitor&) { return *this; } + enum SphereHalf { SphereTopHalf, SphereBottomHalf }; + + // helpers for apply( Cylinder | Sphere | Capsule ) + void drawCylinderBody(unsigned int numSegments, float radius, float height); + void drawHalfSphere(unsigned int numSegments, unsigned int numRows, float radius, SphereHalf which, float zOffset = 0.0f); +}; + +osg::Geometry* convertShapeToGeometry(const osg::Shape& shape, const osg::TessellationHints* hints); + +osg::Geometry* convertShapeToGeometry(const osg::Shape& shape, const osg::TessellationHints* hints, const osg::Vec4& color); + +osg::Geode* convertShapeToGeode(const osg::Shape& shape, const osg::TessellationHints* hints); + +osg::Geode* convertShapeToGeode(const osg::Shape& shape, const osg::TessellationHints* hints, const osg::Vec4& color); + + +// example : how to use convertShapeToGeometry() +// osg::ref_ptr shape = new osg::Capsule( osg::Vec3( 0.0, 0.0, 0.0 ), radius, height ); +// osg::ref_ptr tessHints = new osg::TessellationHints; +// tessHints->setDetailRatio(0.5f); +// tessHints->setCreateTextureCoords(true); +// osg::ref_ptr capsuleGeometry = convertShapeToGeometry(*shape.get(), tessHints.get()); +// osg::ref_ptr redCapsuleGeometry = convertShapeToGeometry(*shape.get(), tessHints.get(), osg::Vec4(1.0,0.0,0.0,1.0) ); + +class ShapesToGeometriesVisitor : public osg::NodeVisitor +{ +public: + ShapesToGeometriesVisitor( osg::TessellationHints* hints ) + : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), _hints(hints) + { + } + void apply( osg::Geode& geode); + +protected: + osg::TessellationHints* _hints; +}; + +#endif diff --git a/examples/osggpucull/osggpucull.cpp b/examples/osggpucull/osggpucull.cpp new file mode 100644 index 000000000..2201e02ee --- /dev/null +++ b/examples/osggpucull/osggpucull.cpp @@ -0,0 +1,1536 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2014 Pawel Ksiezopolski + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ShapeToGeometry.h" +#include "AggregateGeometryVisitor.h" +#include "DrawIndirectPrimitiveSet.h" +#include "GpuCullShaders.h" + +#ifndef GL_RASTERIZER_DISCARD + #define GL_RASTERIZER_DISCARD 0x8C89 +#endif + +// each instance type may have max 8 LODs ( if you change +// this value, dont forget to change it in vertex shaders accordingly ) +const unsigned int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8; +// during culling each instance may be sent to max 4 indirect targets +const unsigned int OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER = 4; + +// Struct defining LOD data for particular instance type +struct InstanceLOD +{ + InstanceLOD() + : bbMin(FLT_MAX,FLT_MAX,FLT_MAX,1.0f), bbMax(-FLT_MAX,-FLT_MAX,-FLT_MAX,1.0f) + { + } + InstanceLOD( const InstanceLOD& iLod ) + : bbMin(iLod.bbMin), bbMax(iLod.bbMax), indirectTargetParams(iLod.indirectTargetParams), distances(iLod.distances) + { + } + InstanceLOD& operator=( const InstanceLOD& iLod ) + { + if( &iLod != this ) + { + bbMin = iLod.bbMin; + bbMax = iLod.bbMax; + indirectTargetParams = iLod.indirectTargetParams; + distances = iLod.distances; + } + return *this; + } + + inline void setBoundingBox( const osg::BoundingBox& bbox ) + { + bbMin = osg::Vec4f( bbox.xMin(), bbox.yMin(), bbox.zMin(), 1.0f ); + bbMax = osg::Vec4f( bbox.xMax(), bbox.yMax(), bbox.zMax(), 1.0f ); + } + inline osg::BoundingBox getBoundingBox() + { + return osg::BoundingBox( bbMin.x(), bbMin.y(), bbMin.z(), bbMax.x(), bbMax.y(), bbMax.z() ); + } + + osg::Vec4f bbMin; // LOD bounding box + osg::Vec4f bbMax; + osg::Vec4i indirectTargetParams; // x=lodIndirectCommand, y=lodIndirectCommandIndex, z=offsetsInTarget, w=lodMaxQuantity + osg::Vec4f distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance +}; + +// Struct defining information about specific instance type : bounding box, lod ranges, indirect target indices etc +struct InstanceType +{ + InstanceType() + : bbMin(FLT_MAX,FLT_MAX,FLT_MAX,1.0f), bbMax(-FLT_MAX,-FLT_MAX,-FLT_MAX,1.0f) + { + params.x() = 0; // this variable defines the number of LODs + for(unsigned int i=0;i= OSGGPUCULL_MAXIMUM_LOD_NUMBER) + return; + params.x() = osg::maximum( params.x(), i+1); + lods[i].indirectTargetParams = osg::Vec4i( targetID, indexInTarget, offsetInTarget, maxQuantity ); + lods[i].distances = distance; + lods[i].setBoundingBox( lodBBox ); + expandBy(lodBBox); + } + inline void expandBy( const osg::BoundingBox& bbox ) + { + osg::BoundingBox myBBox = getBoundingBox(); + myBBox.expandBy(bbox); + setBoundingBox(myBBox); + } + inline void setBoundingBox( const osg::BoundingBox& bbox ) + { + bbMin = osg::Vec4f( bbox.xMin(), bbox.yMin(), bbox.zMin(), 1.0f ); + bbMax = osg::Vec4f( bbox.xMax(), bbox.yMax(), bbox.zMax(), 1.0f ); + } + inline osg::BoundingBox getBoundingBox() + { + return osg::BoundingBox( bbMin.x(), bbMin.y(), bbMin.z(), bbMax.x(), bbMax.y(), bbMax.z() ); + } + + osg::Vec4f bbMin; // bounding box that includes all LODs + osg::Vec4f bbMax; + osg::Vec4i params; // x=number of active LODs + InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER]; // information about LODs +}; + +// CPU side representation of a struct defined in ARB_draw_indirect extension +struct DrawArraysIndirectCommand +{ + DrawArraysIndirectCommand() + : count(0), primCount(0), first(0), baseInstance(0) + { + } + DrawArraysIndirectCommand( unsigned int aFirst, unsigned int aCount ) + : count(aCount), primCount(0), first(aFirst), baseInstance(0) + { + } + unsigned int count; + unsigned int primCount; + unsigned int first; + unsigned int baseInstance; +}; + +// During the first phase of instance rendering cull shader places information about +// instance LODs in texture buffers called "indirect targets" +// All data associated with the indirect target is placed in struct defined below +// ( like for example - draw shader associated with specific indirect target. +// Draw shader performs second phase of instance rendering - the actual rendering of objects +// to screen or to frame buffer object ). +struct IndirectTarget +{ + IndirectTarget() + : maxTargetQuantity(0) + { + indirectCommands = new osg::BufferTemplate< std::vector >; + } + IndirectTarget( AggregateGeometryVisitor* agv, osg::Program* program ) + : geometryAggregator(agv), drawProgram(program), maxTargetQuantity(0) + { + indirectCommands = new osg::BufferTemplate< std::vector >; + } + void endRegister(unsigned int index, unsigned int rowsPerInstance, GLenum pixelFormat, GLenum type, GLint internalFormat, bool useMultiDrawArraysIndirect ) + { + osg::Image* indirectCommandImage = new osg::Image; + indirectCommandImage->setImage( indirectCommands->getTotalDataSize()/sizeof(unsigned int), 1, 1, GL_R32I, GL_RED, GL_UNSIGNED_INT, (unsigned char*)indirectCommands->getDataPointer(), osg::Image::NO_DELETE ); + indirectCommandTextureBuffer = new osg::TextureBuffer(indirectCommandImage); + indirectCommandTextureBuffer->setInternalFormat( GL_R32I ); + indirectCommandTextureBuffer->setUsageHint(GL_DYNAMIC_DRAW); + indirectCommandTextureBuffer->bindToImageUnit(index, osg::Texture::READ_WRITE); + indirectCommandTextureBuffer->setUnRefImageDataAfterApply(false); + + // add proper primitivesets to geometryAggregators + if( !useMultiDrawArraysIndirect ) // use glDrawArraysIndirect() + { + std::vector newPrimitiveSets; + + for(unsigned int j=0;jgetData().size(); ++j) + newPrimitiveSets.push_back( new DrawArraysIndirect( GL_TRIANGLES, indirectCommandTextureBuffer.get(), j*sizeof( DrawArraysIndirectCommand ) ) ); + geometryAggregator->getAggregatedGeometry()->removePrimitiveSet(0,geometryAggregator->getAggregatedGeometry()->getNumPrimitiveSets() ); + for(unsigned int j=0;jgetData().size(); ++j) + geometryAggregator->getAggregatedGeometry()->addPrimitiveSet( newPrimitiveSets[j] ); + } + else // use glMultiDrawArraysIndirect() + { + geometryAggregator->getAggregatedGeometry()->removePrimitiveSet(0,geometryAggregator->getAggregatedGeometry()->getNumPrimitiveSets() ); + geometryAggregator->getAggregatedGeometry()->addPrimitiveSet( new MultiDrawArraysIndirect( GL_TRIANGLES, indirectCommandTextureBuffer.get(), 0, indirectCommands->getData().size(), 0 ) ); + } + geometryAggregator->getAggregatedGeometry()->setUseVertexBufferObjects(true); + geometryAggregator->getAggregatedGeometry()->setUseDisplayList(false); + + osg::Image* instanceTargetImage = new osg::Image; + instanceTargetImage->allocateImage( maxTargetQuantity*rowsPerInstance, 1, 1, pixelFormat, type ); + instanceTarget = new osg::TextureBuffer(instanceTargetImage); + instanceTarget->setInternalFormat( internalFormat ); + instanceTarget->setUsageHint(GL_DYNAMIC_DRAW); + instanceTarget->bindToImageUnit(OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+index, osg::Texture::READ_WRITE); + + } + void addIndirectCommandData( const std::string& uniformNamePrefix, int index, osg::StateSet* stateset ) + { + std::string uniformName = uniformNamePrefix + char( '0' + index ); + osg::Uniform* uniform = new osg::Uniform(uniformName.c_str(), (int)index ); + stateset->addUniform( uniform ); + stateset->setTextureAttribute( index, indirectCommandTextureBuffer.get() ); + } + void addIndirectTargetData( bool cullPhase, const std::string& uniformNamePrefix, int index, osg::StateSet* stateset ) + { + std::string uniformName; + if( cullPhase ) + uniformName = uniformNamePrefix + char( '0' + index ); + else + uniformName = uniformNamePrefix; + + osg::Uniform* uniform = new osg::Uniform(uniformName.c_str(), (int)(OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+index) ); + stateset->addUniform( uniform ); + stateset->setTextureAttribute( OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+index, instanceTarget.get() ); + } + void addDrawProgram( const std::string& uniformBlockName, osg::StateSet* stateset ) + { + drawProgram->addBindUniformBlock(uniformBlockName, 1); + stateset->setAttributeAndModes( drawProgram.get(), osg::StateAttribute::ON ); + } + + osg::ref_ptr< osg::BufferTemplate< std::vector > > indirectCommands; + osg::ref_ptr indirectCommandTextureBuffer; + osg::ref_ptr< AggregateGeometryVisitor > geometryAggregator; + osg::ref_ptr drawProgram; + osg::ref_ptr< osg::TextureBuffer > instanceTarget; + unsigned int maxTargetQuantity; +}; + +// This is the main structure holding all information about particular 2-phase instance rendering +// ( instance types, indirect targets, etc ). +struct GPUCullData +{ + GPUCullData() + { + useMultiDrawArraysIndirect = false; + instanceTypes = new osg::BufferTemplate< std::vector >; + // build Uniform BufferObject with instanceTypes data + instanceTypesUBO = new osg::UniformBufferObject; +// instanceTypesUBO->setUsage( GL_STREAM_DRAW ); + instanceTypes->setBufferObject( instanceTypesUBO.get() ); + instanceTypesUBB = new osg::UniformBufferBinding(1, instanceTypesUBO.get(), 0, 0); + + } + void setUseMultiDrawArraysIndirect( bool value ) + { + useMultiDrawArraysIndirect = value; + } + + void registerIndirectTarget( unsigned int index, AggregateGeometryVisitor* agv, osg::Program* targetDrawProgram ) + { + if(index>=OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER || agv==NULL || targetDrawProgram==NULL ) + return; + targets[index] = IndirectTarget( agv, targetDrawProgram ); + } + bool registerType(unsigned int typeID, unsigned int targetID, osg::Node* node, const osg::Vec4& lodDistances, float maxDensityPerSquareKilometer ) + { + if( typeID >= instanceTypes->getData().size() ) + instanceTypes->getData().resize(typeID+1); + InstanceType& itd = instanceTypes->getData().at(typeID); + unsigned int lodNumber = (unsigned int)itd.params.x(); + if( lodNumber >= OSGGPUCULL_MAXIMUM_LOD_NUMBER ) + return false; + + std::map::iterator target = targets.find(targetID); + if( target==targets.end() ) + return false; + + // AggregateGeometryVisitor creates single osg::Geometry from all objects used by specific indirect target + AggregateGeometryVisitor::AddObjectResult aoResult = target->second.geometryAggregator->addObject( node , typeID, lodNumber ); + // Information about first vertex and a number of vertices is stored for later primitiveset creation + target->second.indirectCommands->getData().push_back( DrawArraysIndirectCommand( aoResult.first, aoResult.count ) ); + + osg::ComputeBoundsVisitor cbv; + node->accept(cbv); + + // Indirect target texture buffers have finite size, therefore each instance LOD has maximum number that may be rendered in one frame. + // This maximum number of rendered instances is estimated from the area that LOD covers and maximum density of instances per square kilometer. + float lodArea = osg::PI * ( lodDistances.w() * lodDistances.w() - lodDistances.x() * lodDistances.x() ) / 1000000.0f; + // calculate max quantity of objects in lodArea using maximum density per square kilometer + unsigned int maxQuantity = (unsigned int) ceil( lodArea * maxDensityPerSquareKilometer ); + + itd.setLodDefinition( lodNumber, targetID, aoResult.index, lodDistances, target->second.maxTargetQuantity, maxQuantity, cbv.getBoundingBox() ) ; + target->second.maxTargetQuantity += maxQuantity; + return true; + } + // endRegister() method is called after all indirect targets and instance types are registered. + // It creates indirect targets with pixel format and data type provided by user ( indirect targets may hold + // different information about single instance depending on user's needs ( in our example : static rendering + // sends all vertex data to indirect target during GPU cull phase, while dynamic rendering sends only a "pointer" + // to texture buffer containing instance data ( look at endRegister() invocations in createStaticRendering() and + // createDynamicRendering() ) + void endRegister( unsigned int rowsPerInstance, GLenum pixelFormat, GLenum type, GLint internalFormat ) + { + OSG_INFO<<"instance types"<getData().size(); ++i) + { + InstanceType& iType = instanceTypes->getData().at(i); + int sum = 0; + OSG_INFO<<"Type "<" << iType.lods[j].indirectTargetParams.w() << " "; + sum += iType.lods[j].indirectTargetParams.w(); + } + OSG_INFO<< ") => " << sum << " elements"<::iterator it,eit; + for(it=targets.begin(), eit=targets.end(); it!=eit; ++it) + { + for(unsigned j=0; jsecond.indirectCommands->getData().size(); ++j) + { + DrawArraysIndirectCommand& iComm = it->second.indirectCommands->getData().at(j); + OSG_INFO<<"("<second.maxTargetQuantity * sizeof(osg::Vec4); + OSG_INFO<<" => Maximum elements in target : "<< it->second.maxTargetQuantity <<" ( "<< sizeInBytes <<" bytes, " << sizeInBytes/1024<< " kB )" << std::endl; + } + + instanceTypesUBB->setSize( instanceTypes->getTotalDataSize() ); + for(it=targets.begin(), eit=targets.end(); it!=eit; ++it) + it->second.endRegister(it->first,rowsPerInstance,pixelFormat,type,internalFormat,useMultiDrawArraysIndirect); + + } + + bool useMultiDrawArraysIndirect; + osg::ref_ptr< osg::BufferTemplate< std::vector > > instanceTypes; + osg::ref_ptr instanceTypesUBO; + osg::ref_ptr instanceTypesUBB; + + std::map targets; +}; + +// StaticInstance struct represents data of a single static instance : +// its position, type, identification and additional params +// ( params are user dependent. In our example params are used +// to describe color, waving amplitude, waving frequency and waving offset ) +struct StaticInstance +{ + StaticInstance( unsigned int typeID, unsigned int id, const osg::Matrixf& m, const osg::Vec4& params ) + : position(m), extraParams(params), idParams(typeID,id,0,0) + { + } + osg::Vec3d getPosition() const + { + return position.getTrans(); + } + osg::Matrixf position; + osg::Vec4f extraParams; + osg::Vec4i idParams; +}; + +// DynamicInstance ( compared to StaticInstance ) holds additional data, like "bones" used to +// animate wheels and propellers in the example + +const unsigned int OSGGPUCULL_MAXIMUM_BONES_NUMBER = 8; + +struct DynamicInstance +{ + DynamicInstance( unsigned int typeID, unsigned int id, const osg::Matrixf& m, const osg::Vec4& params ) + : position(m), extraParams(params), idParams(typeID,id,0,0) + { + for(unsigned int i=0; i +class InstanceCell : public osg::Referenced +{ +public: + typedef std::vector< osg::ref_ptr > > InstanceCellList; + + InstanceCell(): _parent(0) {} + + InstanceCell(osg::BoundingBox& bb):_parent(0), _bb(bb) {} + + void addCell(InstanceCell* cell) { cell->_parent=this; _cells.push_back(cell); } + + void computeBound(); + + bool contains(const osg::Vec3& position) const { return _bb.contains(position); } + + bool divide(unsigned int maxNumInstancesPerCell=100); + + bool divide(bool xAxis, bool yAxis, bool zAxis); + + void bin(); + + InstanceCell* _parent; + osg::BoundingBox _bb; + InstanceCellList _cells; + std::vector _instances; +}; + +template +void InstanceCell::computeBound() +{ + _bb.init(); + for(typename InstanceCellList::iterator citr=_cells.begin(); + citr!=_cells.end(); + ++citr) + { + (*citr)->computeBound(); + _bb.expandBy((*citr)->_bb); + } + + for(typename std::vector::iterator titr=_instances.begin(); + titr!=_instances.end(); ++titr) + { + _bb.expandBy( titr->getPosition() ); + } +} + +template +bool InstanceCell::divide(unsigned int maxNumInstancesPerCell) +{ + if (_instances.size()<=maxNumInstancesPerCell) return false; + + computeBound(); + + float radius = _bb.radius(); + float divide_distance = radius*0.7f; + if (divide((_bb.xMax()-_bb.xMin())>divide_distance,(_bb.yMax()-_bb.yMin())>divide_distance,(_bb.zMax()-_bb.zMin())>divide_distance)) + { + // recusively divide the new cells till maxNumInstancesPerCell is met. + for(typename InstanceCellList::iterator citr=_cells.begin(); citr!=_cells.end(); ++citr) + { + (*citr)->divide(maxNumInstancesPerCell); + } + return true; + } + else + { + return false; + } +} + +template +bool InstanceCell::divide(bool xAxis, bool yAxis, bool zAxis) +{ + if (!(xAxis || yAxis || zAxis)) return false; + + if (_cells.empty()) + _cells.push_back(new InstanceCell(_bb)); + + if (xAxis) + { + unsigned int numCellsToDivide=_cells.size(); + for(unsigned int i=0;i_bb); + + float xCenter = (orig_cell->_bb.xMin()+orig_cell->_bb.xMax())*0.5f; + orig_cell->_bb.xMax() = xCenter; + new_cell->_bb.xMin() = xCenter; + + _cells.push_back(new_cell); + } + } + + if (yAxis) + { + unsigned int numCellsToDivide=_cells.size(); + for(unsigned int i=0;i_bb); + + float yCenter = (orig_cell->_bb.yMin()+orig_cell->_bb.yMax())*0.5f; + orig_cell->_bb.yMax() = yCenter; + new_cell->_bb.yMin() = yCenter; + + _cells.push_back(new_cell); + } + } + + if (zAxis) + { + unsigned int numCellsToDivide=_cells.size(); + for(unsigned int i=0;i_bb); + + float zCenter = (orig_cell->_bb.zMin()+orig_cell->_bb.zMax())*0.5f; + orig_cell->_bb.zMax() = zCenter; + new_cell->_bb.zMin() = zCenter; + + _cells.push_back(new_cell); + } + } + + bin(); + + return true; + +} + +template +void InstanceCell::bin() +{ + // put treeste cells. + std::vector instancesNotAssigned; + for(typename std::vector::iterator titr=_instances.begin(); titr!=_instances.end(); ++titr) + { + osg::Vec3 iPosition = titr->getPosition(); + bool assigned = false; + for(typename InstanceCellList::iterator citr=_cells.begin(); + citr!=_cells.end() && !assigned; + ++citr) + { + if ((*citr)->contains(iPosition)) + { + (*citr)->_instances.push_back(*titr); + assigned = true; + } + } + if (!assigned) instancesNotAssigned.push_back(*titr); + } + + // put the unassigned trees back into the original local tree list. + _instances.swap(instancesNotAssigned); + + // prune empty cells. + InstanceCellList cellsNotEmpty; + for(typename InstanceCellList::iterator citr=_cells.begin(); citr!=_cells.end(); ++citr) + { + if (!((*citr)->_instances.empty())) + { + cellsNotEmpty.push_back(*citr); + } + } + _cells.swap(cellsNotEmpty); + +} + +// Every geometry in the static instance tree stores matrix and additional parameters on the vertex attributtes number 10-15. +osg::Geometry* buildGPUCullGeometry( const std::vector& instances ) +{ + osg::Vec3Array* vertexArray = new osg::Vec3Array; + + osg::Vec4Array* attrib10 = new osg::Vec4Array; + osg::Vec4Array* attrib11 = new osg::Vec4Array; + osg::Vec4Array* attrib12 = new osg::Vec4Array; + osg::Vec4Array* attrib13 = new osg::Vec4Array; + osg::Vec4Array* attrib14 = new osg::Vec4Array; + osg::Vec4Array* attrib15 = new osg::Vec4Array; + + osg::BoundingBox bbox; + std::vector::const_iterator it,eit; + for(it=instances.begin(), eit=instances.end(); it!=eit; ++it) + { + vertexArray->push_back( it->getPosition() ); + attrib10->push_back( osg::Vec4( it->position(0,0), it->position(0,1), it->position(0,2), it->position(0,3) ) ); + attrib11->push_back( osg::Vec4( it->position(1,0), it->position(1,1), it->position(1,2), it->position(1,3) ) ); + attrib12->push_back( osg::Vec4( it->position(2,0), it->position(2,1), it->position(2,2), it->position(2,3) ) ); + attrib13->push_back( osg::Vec4( it->position(3,0), it->position(3,1), it->position(3,2), it->position(3,3) ) ); + attrib14->push_back( it->extraParams ); + attrib15->push_back( osg::Vec4( it->idParams.x(), it->idParams.y(), 0.0, 0.0 ) ); + + bbox.expandBy( it->getPosition() ); + } + + osg::ref_ptr geom = new osg::Geometry; + geom->setVertexArray(vertexArray); + geom->setVertexAttribArray(10, attrib10, osg::Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(11, attrib11, osg::Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(12, attrib12, osg::Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(13, attrib13, osg::Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(14, attrib14, osg::Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(15, attrib15, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, instances.size() ) ); + + geom->setInitialBound( bbox ); + + return geom.release(); +} + +template +osg::Node* createInstanceGraph(InstanceCell* cell, const osg::BoundingBox& objectsBBox ) +{ + bool needGroup = !(cell->_cells.empty()); + bool needInstances = !(cell->_instances.empty()); + + osg::Geode* geode = 0; + osg::Group* group = 0; + + if (needInstances) + { + osg::Geometry* geometry = buildGPUCullGeometry(cell->_instances); + // buildGPUCullGeometry() function creates initial bound using points + // it must be enlarged by bounding boxes of all instance types + osg::BoundingBox bbox = geometry->getInitialBound(); + bbox.xMin() += objectsBBox.xMin(); + bbox.xMax() += objectsBBox.xMax(); + bbox.yMin() += objectsBBox.yMin(); + bbox.yMax() += objectsBBox.yMax(); + bbox.zMin() += objectsBBox.zMin(); + bbox.zMax() += objectsBBox.zMax(); + geometry->setInitialBound(bbox); + geode = new osg::Geode; + geode->addDrawable( geometry ); + } + + if (needGroup) + { + group = new osg::Group; + for(typename InstanceCell::InstanceCellList::iterator itr=cell->_cells.begin(); itr!=cell->_cells.end(); ++itr) + { + group->addChild(createInstanceGraph(itr->get(),objectsBBox)); + } + + if (geode) group->addChild(geode); + + } + if (group) return group; + else return geode; +} + +template +osg::Node* createInstanceTree(const std::vector& instances, const osg::BoundingBox& objectsBBox, unsigned int maxNumInstancesPerCell ) +{ + osg::ref_ptr > rootCell = new InstanceCell(); + rootCell->_instances = instances; + rootCell->divide( maxNumInstancesPerCell ); + + osg::ref_ptr resultNode = createInstanceGraph( rootCell.get(), objectsBBox ); + return resultNode.release(); +} + +// Texture buffers holding information about the number of instances to render ( named "indirect command +// texture buffers", or simply - indirect commands ) must reset instance number to 0 in the beggining of each frame. +// It is done by simple texture reload from osg::Image. +// Moreover - texture buffers that use texture images ( i mean "images" as defined in ARB_shader_image_load_store extension ) +// should call glBindImageTexture() before every shader that uses imageLoad(), imageStore() and imageAtomic*() GLSL functions. +// It looks like glBindImageTexture() should be used the same way the glBindTexture() is used. +struct ResetTexturesCallback : public osg::StateSet::Callback +{ + ResetTexturesCallback() + { + } + void addTextureDirty( unsigned int texUnit ) + { + texUnitsDirty.push_back(texUnit); + } + void addTextureDirtyParams( unsigned int texUnit ) + { + texUnitsDirtyParams.push_back(texUnit); + } + + virtual void operator() (osg::StateSet* stateset, osg::NodeVisitor* nv) + { + std::vector::iterator it,eit; + for(it=texUnitsDirty.begin(), eit=texUnitsDirty.end(); it!=eit; ++it) + { + osg::Texture* tex = dynamic_cast( stateset->getTextureAttribute(*it,osg::StateAttribute::TEXTURE) ); + if(tex==NULL) + continue; + osg::Image* img = tex->getImage(0); + if(img!=NULL) + img->dirty(); + } + for(it=texUnitsDirtyParams.begin(), eit=texUnitsDirtyParams.end(); it!=eit; ++it) + { + osg::Texture* tex = dynamic_cast( stateset->getTextureAttribute(*it,osg::StateAttribute::TEXTURE) ); + if(tex!=NULL) + tex->dirtyTextureParameters(); + } + } + + std::vector texUnitsDirty; + std::vector texUnitsDirtyParams; +}; + +// We must ensure that cull shader finished filling indirect commands and indirect targets, before draw shader +// starts using them. We use glMemoryBarrier() barrier to achieve that. +// It is also possible that we should use glMemoryBarrier() after resetting textures, but i implemented that only for +// dynamic rendering. +struct InvokeMemoryBarrier : public osg::Drawable::DrawCallback +{ + InvokeMemoryBarrier( GLbitfield barriers ) + : _barriers(barriers) + { + } + virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const + { + DrawIndirectGLExtensions *ext = DrawIndirectGLExtensions::getExtensions( renderInfo.getContextID(), true ); + ext->glMemoryBarrier( _barriers ); + drawable->drawImplementation(renderInfo); + } + GLbitfield _barriers; +}; + +osg::Program* createProgram( const std::string& name, const std::string& vertexSource, const std::string& fragmentSource ) +{ + osg::ref_ptr program = new osg::Program; + program->setName( name ); + + osg::ref_ptr vertexShader = new osg::Shader(osg::Shader::VERTEX, vertexSource); + vertexShader->setName( name + "_vertex" ); + program->addShader(vertexShader.get()); + + osg::ref_ptr fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, fragmentSource); + fragmentShader->setName( name + "_fragment" ); + program->addShader(fragmentShader.get()); + + return program.release(); +} + +float random(float min,float max) { return min + (max-min)*(float)rand()/(float)RAND_MAX; } + +osg::Group* createConiferTree( float detailRatio, const osg::Vec4& leafColor, const osg::Vec4& trunkColor ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::Group; + + osg::ref_ptr trunk = new osg::Cylinder( osg::Vec3( 0.0, 0.0, 1.0 ), 0.25, 2.0 ); + osg::ref_ptr trunkGeode = convertShapeToGeode( *trunk.get(), tessHints.get(), trunkColor ); + root->addChild( trunkGeode.get() ); + + osg::ref_ptr shapeCone = new osg::Cone( osg::Vec3( 0.0, 0.0, 4.0 ), 2.0, 8.0 ); + osg::ref_ptr shapeGeode = convertShapeToGeode( *shapeCone.get(), tessHints.get(), leafColor ); + root->addChild( shapeGeode.get() ); + + return root.release(); +} + +// Few functions that create geometries of objects used in example. +// Vertex size in all objects is controlled using single float parameter ( detailRatio ) +// Thanks to this ( and "--triangle-modifier" application parameter ) we may experiment with +// triangle quantity of the scene and how it affects the time statistics +osg::Group* createDecidousTree( float detailRatio, const osg::Vec4& leafColor, const osg::Vec4& trunkColor ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::Group; + + osg::ref_ptr trunk = new osg::Cylinder( osg::Vec3( 0.0, 0.0, 1.0 ), 0.4, 2.0 ); + osg::ref_ptr trunkGeode = convertShapeToGeode( *trunk.get(), tessHints.get(), trunkColor ); + root->addChild( trunkGeode.get() ); + + osg::ref_ptr shapeCapsule = new osg::Capsule( osg::Vec3( 0.0, 0.0, 7.4 ), 3.0, 5.0 ); + osg::ref_ptr shapeGeode = convertShapeToGeode( *shapeCapsule.get(), tessHints.get(), leafColor ); + root->addChild( shapeGeode.get() ); + + return root.release(); +} + + +osg::Group* createSimpleHouse( float detailRatio, const osg::Vec4& buildingColor, const osg::Vec4& chimneyColor ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::Group; + + osg::ref_ptr building = new osg::Box( osg::Vec3( 0.0, 0.0, 8.0 ), 15.0, 9.0, 16.0 ); + osg::ref_ptr buildingGeode = convertShapeToGeode( *building.get(), tessHints.get(), buildingColor ); + root->addChild( buildingGeode.get() ); + + // osg::Box always creates geometry with 12 triangles, so to differentiate building LODs we will add three "chimneys" + { + osg::ref_ptr chimney = new osg::Cylinder( osg::Vec3( -6.0, 3.0, 16.75 ), 0.1, 1.5 ); + osg::ref_ptr chimneyGeode = convertShapeToGeode( *chimney.get(), tessHints.get(), chimneyColor ); + root->addChild( chimneyGeode.get() ); + } + { + osg::ref_ptr chimney = new osg::Cylinder( osg::Vec3( -5.5, 3.0, 16.5 ), 0.1, 1.0 ); + osg::ref_ptr chimneyGeode = convertShapeToGeode( *chimney.get(), tessHints.get(), chimneyColor ); + root->addChild( chimneyGeode.get() ); + } + { + osg::ref_ptr chimney = new osg::Cylinder( osg::Vec3( -5.0, 3.0, 16.25 ), 0.1, 0.5 ); + osg::ref_ptr chimneyGeode = convertShapeToGeode( *chimney.get(), tessHints.get(), chimneyColor ); + root->addChild( chimneyGeode.get() ); + } + + return root.release(); +} + +osg::MatrixTransform* createPropeller( float detailRatio, int propNum, float propRadius, const osg::Vec4& color ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::MatrixTransform; + osg::ref_ptr center = new osg::Cone( osg::Vec3( 0.0, 0.0, 0.05 ), 0.1*propRadius, 0.25*propRadius ); + osg::ref_ptr centerGeode = convertShapeToGeode( *center.get(), tessHints.get(), color ); + osg::ref_ptr prop = new osg::Cone( osg::Vec3( 0.0, 0.0, -0.75*propRadius ), 0.1*propRadius, 1.0*propRadius ); + osg::ref_ptr propGeode = convertShapeToGeode( *prop.get(), tessHints.get(), color ); + + root->addChild( centerGeode.get() ); + for( int i=0; i propMt = new osg::MatrixTransform( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0) ) * osg::Matrix::scale(1.0,1.0,0.3) * osg::Matrix::rotate( angle, osg::Vec3(0.0,0.0,1.0) ) ); + propMt->addChild( propGeode.get() ); + root->addChild( propMt.get() ); + } + return root.release(); +} + + +osg::Group* createBlimp( float detailRatio, const osg::Vec4& hullColor, const osg::Vec4& propColor ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::Group; + osg::ref_ptr hull = new osg::Capsule( osg::Vec3( 0.0, 0.0, 0.0 ), 5.0, 10.0 ); + osg::ref_ptr hullGeode = convertShapeToGeode( *hull.get(), tessHints.get(), hullColor ); + + osg::ref_ptr gondola = new osg::Capsule( osg::Vec3( 5.5, 0.0, 0.0 ), 1.0, 6.0 ); + osg::ref_ptr gondolaGeode = convertShapeToGeode( *gondola.get(), tessHints.get(), hullColor ); + + osg::ref_ptr rudderV = new osg::Box( osg::Vec3( 0.0, 0.0, -10.0 ), 8.0, 0.3, 4.0 ); + osg::ref_ptr rudderVGeode = convertShapeToGeode( *rudderV.get(), tessHints.get(), hullColor ); + osg::ref_ptr rudderH = new osg::Box( osg::Vec3( 0.0, 0.0, -10.0 ), 0.3, 8.0, 4.0 ); + osg::ref_ptr rudderHGeode = convertShapeToGeode( *rudderH.get(), tessHints.get(), hullColor ); + + osg::Matrix m; + m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)); + osg::ref_ptr hullMt = new osg::MatrixTransform(m); + hullMt->addChild( hullGeode.get() ); + hullMt->addChild( gondolaGeode.get() ); + hullMt->addChild( rudderVGeode.get() ); + hullMt->addChild( rudderHGeode.get() ); + root->addChild( hullMt.get() ); + + osg::ref_ptr propellerLeft = createPropeller( detailRatio, 4, 1.0, propColor ); + propellerLeft->setMatrix( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,2.0,-6.0) ); + propellerLeft->setName("prop0"); root->addChild( propellerLeft.get() ); + + osg::ref_ptr propellerRight = createPropeller( detailRatio, 4, 1.0, propColor ); + propellerRight->setMatrix( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,-2.0,-6.0) ); + propellerRight->setName("prop1"); root->addChild( propellerRight.get() ); + + return root.release(); +} + +osg::Group* createCar( float detailRatio, const osg::Vec4& hullColor, const osg::Vec4& wheelColor ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::Group; + osg::ref_ptr wheel = new osg::Cylinder( osg::Vec3( 0.0, 0.0, 0.0 ), 1.0, 0.6 ); + osg::ref_ptr wheelGeom = convertShapeToGeometry( *wheel.get(), tessHints.get(), wheelColor ); + // one random triangle on every wheel will use black color to show that wheel is rotating + osg::Vec4Array* colorArray = dynamic_cast( wheelGeom->getColorArray() ); + if(colorArray!=NULL) + { + unsigned int triCount = colorArray->size() / 3; + unsigned int randomTriangle = random(0,triCount); + for(unsigned int i=0;i<3;++i) + (*colorArray)[3*randomTriangle+i] = osg::Vec4(0.0,0.0,0.0,1.0); + } + osg::ref_ptr wheelGeode = new osg::Geode; + wheelGeode->addDrawable( wheelGeom.get() ); + osg::ref_ptr hull = new osg::Box( osg::Vec3( 0.0, 0.0, 1.3 ), 5.0, 3.0, 1.4 ); + osg::ref_ptr hullGeode = convertShapeToGeode( *hull.get(), tessHints.get(), hullColor ); + root->addChild( hullGeode.get() ); + + osg::Matrix m; + m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,1.8,1.0); + osg::ref_ptr wheel0Mt = new osg::MatrixTransform(m); + wheel0Mt->setName("wheel0"); wheel0Mt->addChild( wheelGeode.get() ); root->addChild( wheel0Mt.get() ); + + m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,1.8,1.0); + osg::ref_ptr wheel1Mt = new osg::MatrixTransform(m); + wheel1Mt->setName("wheel1"); wheel1Mt->addChild( wheelGeode.get() ); root->addChild( wheel1Mt.get() ); + + m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,-1.8,1.0); + osg::ref_ptr wheel2Mt = new osg::MatrixTransform(m); + wheel2Mt->setName("wheel2"); wheel2Mt->addChild( wheelGeode.get() ); root->addChild( wheel2Mt.get() ); + + m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,-1.8,1.0); + osg::ref_ptr wheel3Mt = new osg::MatrixTransform(m); + wheel3Mt->setName("wheel3"); wheel3Mt->addChild( wheelGeode.get() ); root->addChild( wheel3Mt.get() ); + + return root.release(); +} + +osg::Group* createAirplane( float detailRatio, const osg::Vec4& hullColor, const osg::Vec4& propColor ) +{ + osg::ref_ptr tessHints = new osg::TessellationHints; + tessHints->setCreateTextureCoords(true); + tessHints->setDetailRatio(detailRatio); + + osg::ref_ptr root = new osg::Group; + osg::ref_ptr hull = new osg::Capsule( osg::Vec3( 0.0, 0.0, 0.0 ), 0.8, 6.0 ); + osg::ref_ptr hullGeode = convertShapeToGeode( *hull.get(), tessHints.get(), hullColor ); + + osg::ref_ptr wing0 = new osg::Box( osg::Vec3( 0.4, 0.0, 1.3 ), 0.1, 7.0, 1.6 ); + osg::ref_ptr wing0Geode = convertShapeToGeode( *wing0.get(), tessHints.get(), hullColor ); + osg::ref_ptr wing1 = new osg::Box( osg::Vec3( -1.4, 0.0, 1.5 ), 0.1, 10.0, 1.8 ); + osg::ref_ptr wing1Geode = convertShapeToGeode( *wing1.get(), tessHints.get(), hullColor ); + + osg::ref_ptr rudderV = new osg::Box( osg::Vec3( -0.8, 0.0, -3.9 ), 1.5, 0.05, 1.0 ); + osg::ref_ptr rudderVGeode = convertShapeToGeode( *rudderV.get(), tessHints.get(), hullColor ); + osg::ref_ptr rudderH = new osg::Box( osg::Vec3( -0.2, 0.0, -3.9 ), 0.05, 4.0, 1.0 ); + osg::ref_ptr rudderHGeode = convertShapeToGeode( *rudderH.get(), tessHints.get(), hullColor ); + + osg::Matrix m; + m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)); + osg::ref_ptr hullMt = new osg::MatrixTransform(m); + hullMt->addChild( hullGeode.get() ); + hullMt->addChild( wing0Geode.get() ); + hullMt->addChild( wing1Geode.get() ); + hullMt->addChild( rudderVGeode.get() ); + hullMt->addChild( rudderHGeode.get() ); + root->addChild( hullMt.get() ); + + osg::ref_ptr propeller = createPropeller( detailRatio, 3, 1.6, propColor ); + propeller->setMatrix( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(3.8,0.0,0.0) ); + propeller->setName("prop0"); root->addChild( propeller.get() ); + + return root.release(); +} + +// createStaticRendering() shows how to use any OSG graph ( wheter it is single osg::Geode, or sophisticated osg::PagedLOD tree covering whole earth ) +// as a source of instance data. This way, the OSG graph of arbitrary size is at first culled using typical OSG mechanisms, then remaining osg::Geometries +// are sent to cull shader ( cullProgram ). Cull shader does not draw anything to screen ( thanks to GL_RASTERIZER_DISCARD mode ), but calculates if particular +// instances - sourced from above mentioned osg::Geometries - are visible and what LODs for these instances should be rendered. +// Information about instances is stored into indirect commands ( the number of instances sent to indirect target ) and in indirect targets +// ( static rendering sends all instance data from vertex attributes to indirect target : position, id, extra params, etc ). +// Next the draw shader ( associated with indirect target ) plays its role. The main trick at draw shader invocation is a right use of indirect command. +// Indirect command is bound as a GL_DRAW_INDIRECT_BUFFER and glDrawArraysIndirect() function is called. Thanks to this - proper number +// of instances is rendered without time-consuming GPU->CPU roundtrip. Draw shader renders an aggregate geometry - an osg::Geometry object +// that contains all objects to render ( for specific indirect target ). +void createStaticRendering( osg::Group* root, GPUCullData& gpuData, const osg::Vec2& minArea, const osg::Vec2& maxArea, unsigned int maxNumInstancesPerCell, float lodModifier, float densityModifier, float triangleModifier, bool exportInstanceObjects ) +{ + // Creating objects using ShapeToGeometry - its vertex count my vary according to triangleModifier variable. + // Every object may be stored to file if you want to inspect it ( to show how many vertices it has for example ). + // To do it - use "--export-objects" parameter in commandline. + // Every object LOD has different color to show the place where LODs switch. + osg::ref_ptr coniferTree0 = createConiferTree( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*coniferTree0.get(),"coniferTree0.osgt"); + osg::ref_ptr coniferTree1 = createConiferTree( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*coniferTree1.get(),"coniferTree1.osgt"); + osg::ref_ptr coniferTree2 = createConiferTree( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*coniferTree2.get(),"coniferTree2.osgt"); + + osg::ref_ptr decidousTree0 = createDecidousTree( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*decidousTree0.get(),"decidousTree0.osgt"); + osg::ref_ptr decidousTree1 = createDecidousTree( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*decidousTree1.get(),"decidousTree1.osgt"); + osg::ref_ptr decidousTree2 = createDecidousTree( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*decidousTree2.get(),"decidousTree2.osgt"); + + osg::ref_ptr simpleHouse0 = createSimpleHouse( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*simpleHouse0.get(),"simpleHouse0.osgt"); + osg::ref_ptr simpleHouse1 = createSimpleHouse( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*simpleHouse1.get(),"simpleHouse1.osgt"); + osg::ref_ptr simpleHouse2 = createSimpleHouse( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*simpleHouse2.get(),"simpleHouse2.osgt"); + + // Two indirect targets are registered : the first one renders static objects. The second one is used to render trees waving in the wind. + gpuData.registerIndirectTarget( 0, new AggregateGeometryVisitor( new ConvertTrianglesOperatorClassic() ), createProgram( "static_draw_0", SHADER_STATIC_DRAW_0_VERTEX, SHADER_STATIC_DRAW_0_FRAGMENT ) ); + gpuData.registerIndirectTarget( 1, new AggregateGeometryVisitor( new ConvertTrianglesOperatorClassic() ), createProgram( "static_draw_1", SHADER_STATIC_DRAW_1_VERTEX, SHADER_STATIC_DRAW_1_FRAGMENT ) ); + + float objectDensity[3]; + objectDensity[0] = 10000.0f * densityModifier; + objectDensity[1] = 1000.0f * densityModifier; + objectDensity[2] = 100.0f * densityModifier; + + // Three types of instances are registered - each one with three LODs, but first two LODs for trees will be sent to second indirect target + gpuData.registerType( 0, 1, coniferTree0.get(), osg::Vec4(0.0f,0.0f,100.0f,110.0f ) * lodModifier, objectDensity[0] ); + gpuData.registerType( 0, 1, coniferTree1.get(), osg::Vec4(100.0f,110.0f,500.0f,510.0f) * lodModifier, objectDensity[0] ); + gpuData.registerType( 0, 0, coniferTree2.get(), osg::Vec4(500.0f,510.0f,1200.0f,1210.0f) * lodModifier, objectDensity[0] ); + gpuData.registerType( 1, 1, decidousTree0.get(), osg::Vec4(0.0f,0.0f,120.0f,130.0f ) * lodModifier, objectDensity[1] ); + gpuData.registerType( 1, 1, decidousTree1.get(), osg::Vec4(120.0f,130.0f,600.0f,610.0f) * lodModifier, objectDensity[1] ); + gpuData.registerType( 1, 0, decidousTree2.get(), osg::Vec4(600.0f,610.0f,1400.0f,1410.0f) * lodModifier, objectDensity[1] ); + gpuData.registerType( 2, 0, simpleHouse0.get(), osg::Vec4(0.0f,0.0f,140.0f,150.0f ) * lodModifier, objectDensity[2] ); + gpuData.registerType( 2, 0, simpleHouse1.get(), osg::Vec4(140.0f,150.0f,700.0f,710.0f) * lodModifier, objectDensity[2] ); + gpuData.registerType( 2, 0, simpleHouse2.get(), osg::Vec4(700.0f,710.0f,2000.0f,2010.0f) * lodModifier, objectDensity[2] ); + // every target will store 6 rows of data in GL_RGBA32F_ARB format ( the whole StaticInstance structure ) + gpuData.endRegister(6,GL_RGBA,GL_FLOAT,GL_RGBA32F_ARB); + + // Now we are going to build the tree of instances. Each instance type will be recognized by its TypeID + // All instances will be placed in area (minArea x maxArea) with densities registered in GPUCullData + std::vector instances; + osg::BoundingBox bbox( minArea.x(), minArea.y(), 0.0, maxArea.x(), maxArea.y(), 0.0 ); + float fullArea = ( bbox.xMax() - bbox.xMin() ) * ( bbox.yMax() - bbox.yMin() ); + unsigned int currentID = 0; + // First - all instances are stored in std::vector + for( unsigned int i=0; i<3; ++i) + { + // using LOD area and maximum density - the maximum instance quantity is calculated + int objectQuantity = (int) floor ( objectDensity[i] * fullArea / 1000000.0f ); + for(int j=0; j::iterator iit,ieit; + for(iit=gpuData.instanceTypes->getData().begin(), ieit=gpuData.instanceTypes->getData().end(); iit!=ieit; ++iit) + allObjectsBbox.expandBy(iit->getBoundingBox()); + osg::ref_ptr instancesTree = createInstanceTree(instances, allObjectsBbox, maxNumInstancesPerCell); + if( exportInstanceObjects ) + osgDB::writeNodeFile(*instancesTree.get(),"staticInstancesTree.osgt"); + root->addChild( instancesTree.get() ); + // instance OSG tree is connected to cull shader with all necessary data ( all indirect commands, all + // indirect targets, necessary OpenGl modes etc. ) + { + osg::ref_ptr resetTexturesCallback = new ResetTexturesCallback; + + osg::ref_ptr cullProgram = createProgram( "static_cull", SHADER_STATIC_CULL_VERTEX, SHADER_STATIC_CULL_FRAGMENT ); + cullProgram->addBindUniformBlock("instanceTypesData", 1); + instancesTree->getOrCreateStateSet()->setAttributeAndModes( cullProgram.get(), osg::StateAttribute::ON ); + instancesTree->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); + instancesTree->getOrCreateStateSet()->setMode( GL_RASTERIZER_DISCARD, osg::StateAttribute::ON ); + + std::map::iterator it,eit; + for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) + { + it->second.addIndirectCommandData( "indirectCommand", it->first, instancesTree->getOrCreateStateSet() ); + resetTexturesCallback->addTextureDirty( it->first ); + resetTexturesCallback->addTextureDirtyParams( it->first ); + + it->second.addIndirectTargetData( true, "indirectTarget", it->first, instancesTree->getOrCreateStateSet() ); + resetTexturesCallback->addTextureDirtyParams( OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+it->first ); + } + + osg::Uniform* indirectCommandSize = new osg::Uniform( osg::Uniform::INT, "indirectCommandSize" ); + indirectCommandSize->set( (int)(sizeof(DrawArraysIndirectCommand) / sizeof(unsigned int)) ); + instancesTree->getOrCreateStateSet()->addUniform( indirectCommandSize ); + + instancesTree->getOrCreateStateSet()->setUpdateCallback( resetTexturesCallback.get() ); + } + + // in the end - we create OSG objects that draw instances using indirect targets and commands. + std::map::iterator it,eit; + for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) + { + osg::ref_ptr drawGeode = new osg::Geode; + it->second.geometryAggregator->getAggregatedGeometry()->setDrawCallback( new InvokeMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT) ); + drawGeode->addDrawable( it->second.geometryAggregator->getAggregatedGeometry() ); + drawGeode->setCullingActive(false); + + it->second.addIndirectTargetData( false, "indirectTarget", it->first, drawGeode->getOrCreateStateSet() ); + drawGeode->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); + it->second.addDrawProgram( "instanceTypesData", drawGeode->getOrCreateStateSet() ); + drawGeode->getOrCreateStateSet()->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ) ); + + root->addChild(drawGeode.get()); + } +} + +// Dynamic instances are stored in a single osg::Geometry. This geometry holds only a "pointer" +// to texture buffer that contains all info about particular instance. When we animate instances +// we only change information in this texture buffer +osg::Geometry* buildGPUCullGeometry( const std::vector& instances ) +{ + osg::Vec3Array* vertexArray = new osg::Vec3Array; + osg::Vec4iArray* attrib10 = new osg::Vec4iArray; + + osg::BoundingBox bbox; + std::vector::const_iterator it,eit; + for(it=instances.begin(), eit=instances.end(); it!=eit; ++it) + { + vertexArray->push_back( it->getPosition() ); + attrib10->push_back( it->idParams ); + + bbox.expandBy( it->getPosition() ); + } + + osg::ref_ptr geom = new osg::Geometry; + geom->setVertexArray(vertexArray); + geom->setVertexAttribArray(10, attrib10, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, instances.size() ) ); + + geom->setInitialBound( bbox ); + + return geom.release(); +} + +// To animate dynamic objects we use an update callback. It performs some simple +// instance wandering ( object goes to random destination point and when it reaches +// destination, it picks another random point and so on ). +// Object parts are animated ( wheels and propellers ) +struct AnimateObjectsCallback : public osg::Drawable::UpdateCallback +{ + AnimateObjectsCallback( osg::BufferTemplate< std::vector >* instances, osg::Image* instancesImage, const osg::BoundingBox& bbox, unsigned int quantityPerType ) + : _instances(instances), _instancesImage(instancesImage), _bbox(bbox), _quantityPerType(quantityPerType), _lastTime(0.0) + { + _destination = new osg::Vec2Array; + _destination->reserve(3*_quantityPerType); + unsigned int i; + for(i=0; i<3*_quantityPerType; ++i) + _destination->push_back( osg::Vec2(random( _bbox.xMin(), _bbox.xMax()), random( _bbox.yMin(), _bbox.yMax()) ) ); + i=0; + for(; i<_quantityPerType; ++i) // speed of blimps + _speed.push_back( random( 5.0, 10.0) ); + for(; i<2*_quantityPerType; ++i) // speed of cars + _speed.push_back( random( 1.0, 5.0) ); + for(; i<3*_quantityPerType; ++i) // speed of airplanes + _speed.push_back( random( 10.0, 16.0 ) ); + } + virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable) + { + if( nv->getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR ) + return; + const osg::FrameStamp* frameStamp = nv->getFrameStamp(); + if(frameStamp==NULL) + return; + double currentTime = frameStamp->getSimulationTime(); + double deltaTime = 0.016; + if(_lastTime!=0.0) + deltaTime = currentTime - _lastTime; + _lastTime = currentTime; + + osg::Geometry* geom = dynamic_cast(drawable); + if(geom==NULL) + return; + osg::Vec3Array* vertexArray = dynamic_cast( geom->getVertexArray() ); + if( vertexArray == NULL ) + return; + + + osg::BoundingBox nbbox; + unsigned int i=0; + for(;i<_quantityPerType;++i) // update blimps + { + nbbox.expandBy( updateObjectPosition( vertexArray, i, deltaTime ) ); + // now we update propeler positions + setRotationUsingRotSpeed( i, 5, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,2.0,-6.0), currentTime, 0.5 ); + setRotationUsingRotSpeed( i, 6, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,-2.0,-6.0), currentTime, -0.5 ); + } + for(;i<2*_quantityPerType;++i) //update cars + { + nbbox.expandBy( updateObjectPosition( vertexArray, i, deltaTime ) ); + // calculate car wheel rotation speed measured in rot/sec + double wheelRotSpeed = -1.0 * _speed[i] / ( 2.0*osg::PI ); + setRotationUsingRotSpeed( i, 1, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,1.8,1.0), currentTime, wheelRotSpeed ); + setRotationUsingRotSpeed( i, 2, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,1.8,1.0), currentTime, wheelRotSpeed ); + setRotationUsingRotSpeed( i, 3, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,-1.8,1.0), currentTime, wheelRotSpeed ); + setRotationUsingRotSpeed( i, 4, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,-1.8,1.0), currentTime, wheelRotSpeed ); + } + for(;i<3*_quantityPerType;++i) // update airplanes + { + nbbox.expandBy( updateObjectPosition( vertexArray, i, deltaTime ) ); + setRotationUsingRotSpeed( i, 5, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(3.8,0.0,0.0), currentTime, 1.5 ); + } + geom->setInitialBound( nbbox ); + _instancesImage->dirty(); + } + + osg::Vec3 updateObjectPosition( osg::Vec3Array* vertexArray, unsigned int index, float deltaTime ) + { + osg::Vec3 oldPosition = _instances->getData().at(index).getPosition(); + + osg::Vec2 op2(oldPosition.x(), oldPosition.y() ); + while( ( (*_destination)[index]- op2).length() < 10.0 ) + (*_destination)[index] = osg::Vec2(random( _bbox.xMin(), _bbox.xMax()), random( _bbox.yMin(), _bbox.yMax()) ); + osg::Vec2 direction = (*_destination)[index] - op2; + direction.normalize(); + osg::Vec2 np2 = direction * deltaTime * _speed[index] ; + osg::Vec3 newPosition = oldPosition + osg::Vec3( np2.x(), np2.y(), 0.0 ); + + osg::Quat heading; + heading.makeRotate( osg::Vec3(1.0,0.0,0.0), osg::Vec3(direction.x(), direction.y(), 0.0) ); + _instances->getData().at(index).position.setTrans( newPosition ); + _instances->getData().at(index).position.setRotate( heading ); + (*vertexArray)[index] = newPosition; + return newPosition; + } + void setRotationUsingRotSpeed( unsigned int index, unsigned int boneIndex, const osg::Matrix& zeroMatrix, double currentTime, double rotSpeed ) + { + // setRotationUsingRotSpeed() is a very unoptimally writen ( because it uses osg::Matrix::inverse() ), + // and that is done on purpose : in real life scenario functions making updates may take long time + // to calculate new object positions due to sophisticated physics models, geometry intersections etc. + osg::Matrix mRot = osg::Matrix::rotate( fmod( 2.0 * osg::PI * rotSpeed * currentTime,2.0*osg::PI) , osg::Vec3(0.0,0.0,1.0) ); + _instances->getData().at(index).bones[boneIndex] = osg::Matrix::inverse(zeroMatrix) * mRot * zeroMatrix; + } + + + osg::ref_ptr< osg::BufferTemplate< std::vector > > _instances; + osg::ref_ptr _instancesImage; + osg::BoundingBox _bbox; + unsigned int _quantityPerType; + osg::ref_ptr _destination; + std::vector _speed; + double _lastTime; +}; + +// createDynamicRendering() is similar to earlier createStaticRendering() method. The differences are as follows : +// - instance input data is not stored in osg::Geometry vertex attributes but in dedicated texture buffer ( named "dynamicInstancesData" ). Vertex attributes +// in osg::Geometry store only a "pointer" to that buffer. This pointer is later sent to indirect target by the cull shader. And then - used by draw +// shader to get instance data from "dynamicInstancesData" buffer during rendering. +// - draw shader shows how to animate objects using "bones". Each vertex in rendered geometry is associated with single "bone". Such association is +// sufficient to animate mechanical objects. It is also possible to animate organic objects ( large crowds of people ) but it is far beyond +// the scope of this example. +void createDynamicRendering( osg::Group* root, GPUCullData& gpuData, osg::BufferTemplate< std::vector >* instances, const osg::Vec2& minArea, const osg::Vec2& maxArea, float lodModifier, float densityModifier, float triangleModifier, bool exportInstanceObjects ) +{ + // Creating objects using ShapeToGeometry - its vertex count my vary according to triangleModifier variable. + // Every object may be stored to file if you want to inspect it ( to show how many vertices it has for example ). + // To do it - use "--export-objects" parameter in commandline. + // Every object LOD has different color to show the place where LODs switch. + osg::ref_ptr blimpLod0 = createBlimp( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*blimpLod0.get(),"blimpLod0.osgt"); + osg::ref_ptr blimpLod1 = createBlimp( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*blimpLod1.get(),"blimpLod1.osgt"); + osg::ref_ptr blimpLod2 = createBlimp( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*blimpLod2.get(),"blimpLod2.osgt"); + + osg::ref_ptr carLod0 = createCar( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.3,0.3,0.3,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*carLod0.get(),"carLod0.osgt"); + osg::ref_ptr carLod1 = createCar( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*carLod1.get(),"carLod1.osgt"); + osg::ref_ptr carLod2 = createCar( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*carLod2.get(),"carLod2.osgt"); + + osg::ref_ptr airplaneLod0 = createAirplane( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*airplaneLod0.get(),"airplaneLod0.osgt"); + osg::ref_ptr airplaneLod1 = createAirplane( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*airplaneLod1.get(),"airplaneLod1.osgt"); + osg::ref_ptr airplaneLod2 = createAirplane( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); + if( exportInstanceObjects ) osgDB::writeNodeFile(*airplaneLod2.get(),"airplaneLod2.osgt"); + + // ConvertTrianglesOperatorClassic struct is informed which node names correspond to which bone indices + ConvertTrianglesOperatorClassic* target0Converter = new ConvertTrianglesOperatorClassic(); + target0Converter->registerBoneByName("wheel0",1); + target0Converter->registerBoneByName("wheel1",2); + target0Converter->registerBoneByName("wheel2",3); + target0Converter->registerBoneByName("wheel3",4); + target0Converter->registerBoneByName("prop0",5); + target0Converter->registerBoneByName("prop1",6); + // dynamic rendering uses only one indirect target in this example + gpuData.registerIndirectTarget( 0, new AggregateGeometryVisitor( target0Converter ), createProgram( "dynamic_draw_0", SHADER_DYNAMIC_DRAW_0_VERTEX, SHADER_DYNAMIC_DRAW_0_FRAGMENT ) ); + + float objectDensity = 100.0f * densityModifier; + // all instance types are registered and will render using the same indirect target + gpuData.registerType( 0, 0, blimpLod0.get(), osg::Vec4(0.0f,0.0f,150.0f,160.0f) * lodModifier, objectDensity ); + gpuData.registerType( 0, 0, blimpLod1.get(), osg::Vec4(150.0f,160.0f,800.0f,810.0f) * lodModifier, objectDensity ); + gpuData.registerType( 0, 0, blimpLod2.get(), osg::Vec4(800.0f,810.0f,6500.0f,6510.0f) * lodModifier, objectDensity ); + gpuData.registerType( 1, 0, carLod0.get(), osg::Vec4(0.0f,0.0f,50.0f,60.0f) * lodModifier, objectDensity ); + gpuData.registerType( 1, 0, carLod1.get(), osg::Vec4(50.0f,60.0f,300.0f,310.0f) * lodModifier, objectDensity ); + gpuData.registerType( 1, 0, carLod2.get(), osg::Vec4(300.0f,310.0f,1000.0f,1010.0f) * lodModifier, objectDensity ); + gpuData.registerType( 2, 0, airplaneLod0.get(), osg::Vec4(0.0f,0.0f,80.0f,90.0f) * lodModifier, objectDensity ); + gpuData.registerType( 2, 0, airplaneLod1.get(), osg::Vec4(80.0f,90.0f,400.0f,410.0f) * lodModifier, objectDensity ); + gpuData.registerType( 2, 0, airplaneLod2.get(), osg::Vec4(400.0f,410.0f,1200.0f,1210.0f) * lodModifier, objectDensity ); + // dynamic targets store only a "pointer" to instanceTarget buffer + gpuData.endRegister(1,GL_RGBA,GL_INT, GL_RGBA32I_EXT); + + // add dynamic instances to instances container + float objectZ[3]; + objectZ[0] = 50.0f; + objectZ[1] = 0.0f; + objectZ[2] = 20.0f; + + osg::BoundingBox bbox( minArea.x(), minArea.y(), 0.0, maxArea.x(), maxArea.y(), 0.0 ); + float fullArea = ( bbox.xMax() - bbox.xMin() ) * ( bbox.yMax() - bbox.yMin() ); + unsigned int objectQuantity = (unsigned int) floor ( objectDensity * fullArea / 1000000.0f ); + unsigned int currentID = 0; + + for( unsigned int i=0; i<3; ++i) + { + for(unsigned int j=0; jgetData().push_back(DynamicInstance(i,currentID, osg::Matrixf::rotate(rotation, osg::Vec3(0.0,0.0,1.0) ) * osg::Matrixf::translate(position) , osg::Vec4( brightness, 0.0,0.0,0.0 ) ) ); + currentID++; + } + } + + // all data about instances is stored in texture buffer ( compare it with static rendering ) + osg::Image* instancesImage = new osg::Image; + instancesImage->setImage( instances->getTotalDataSize() / sizeof(osg::Vec4f), 1, 1, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT, (unsigned char*)instances->getDataPointer(), osg::Image::NO_DELETE ); + osg::TextureBuffer* instancesTextureBuffer = new osg::TextureBuffer(instancesImage); + instancesTextureBuffer->setUsageHint(GL_STATIC_DRAW); + instancesTextureBuffer->setUnRefImageDataAfterApply(false); + + osg::Uniform* dynamicInstancesDataUniform = new osg::Uniform( "dynamicInstancesData", 8 ); + osg::Uniform* dynamicInstancesDataSize = new osg::Uniform( osg::Uniform::INT, "dynamicInstancesDataSize" ); + dynamicInstancesDataSize->set( (int)(sizeof(DynamicInstance) / sizeof(osg::Vec4f)) ); + + // all instance "pointers" are stored in a single geometry rendered with cull shader + osg::ref_ptr instanceGeometry = buildGPUCullGeometry( instances->getData() ); + osg::ref_ptr instanceGeode = new osg::Geode; + instanceGeode->addDrawable(instanceGeometry.get()); + if( exportInstanceObjects ) + osgDB::writeNodeFile(*instanceGeode.get(),"dynamicInstanceGeode.osgt"); + + // update callback that animates dynamic objects + instanceGeometry->setUpdateCallback( new AnimateObjectsCallback( instances, instancesImage, bbox, objectQuantity ) ); + instanceGeometry->setDrawCallback( new InvokeMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT) ); + root->addChild( instanceGeode.get() ); + // instance geode is connected to cull shader with all necessary data ( all indirect commands, all + // indirect targets, necessary OpenGL modes etc. ) + { + + instanceGeode->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); + osg::ref_ptr resetTexturesCallback = new ResetTexturesCallback; + osg::ref_ptr cullProgram = createProgram( "dynamic_cull", SHADER_DYNAMIC_CULL_VERTEX, SHADER_DYNAMIC_CULL_FRAGMENT ); + cullProgram->addBindUniformBlock("instanceTypesData", 1); + instanceGeode->getOrCreateStateSet()->setAttributeAndModes( cullProgram.get(), osg::StateAttribute::ON ); + + instanceGeode->getOrCreateStateSet()->setMode( GL_RASTERIZER_DISCARD, osg::StateAttribute::ON ); + + instanceGeode->getOrCreateStateSet()->setTextureAttribute( 8, instancesTextureBuffer ); + instanceGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataUniform ); + instanceGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataSize ); + + std::map::iterator it,eit; + for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) + { + it->second.addIndirectCommandData( "indirectCommand", it->first, instanceGeode->getOrCreateStateSet() ); + resetTexturesCallback->addTextureDirty( it->first ); + resetTexturesCallback->addTextureDirtyParams( it->first ); + + it->second.addIndirectTargetData( true, "indirectTarget", it->first, instanceGeode->getOrCreateStateSet() ); + resetTexturesCallback->addTextureDirtyParams( OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+it->first ); + } + + osg::Uniform* indirectCommandSize = new osg::Uniform( osg::Uniform::INT, "indirectCommandSize" ); + indirectCommandSize->set( (int)(sizeof(DrawArraysIndirectCommand) / sizeof(unsigned int)) ); + instanceGeode->getOrCreateStateSet()->addUniform( indirectCommandSize ); + + instanceGeode->getOrCreateStateSet()->setUpdateCallback( resetTexturesCallback.get() ); + } + + // in the end - we create OSG objects that draw instances using indirect targets and commands. + std::map::iterator it,eit; + for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) + { + osg::ref_ptr drawGeode = new osg::Geode; + it->second.geometryAggregator->getAggregatedGeometry()->setDrawCallback( new InvokeMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT) ); + drawGeode->addDrawable( it->second.geometryAggregator->getAggregatedGeometry() ); + drawGeode->setCullingActive(false); + + drawGeode->getOrCreateStateSet()->setTextureAttribute( 8, instancesTextureBuffer ); + drawGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataUniform ); + drawGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataSize ); + + it->second.addIndirectTargetData( false, "indirectTarget", it->first, drawGeode->getOrCreateStateSet() ); + drawGeode->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); + it->second.addDrawProgram( "instanceTypesData", drawGeode->getOrCreateStateSet() ); + drawGeode->getOrCreateStateSet()->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ) ); + + root->addChild(drawGeode.get()); + } +} + +int main( int argc, char **argv ) +{ + // use an ArgumentParser object to manage the program arguments. + osg::ArgumentParser arguments(&argc,argv); + + arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the example which demonstrates two-phase geometry instancing using GPU to cull and lod objects"); + arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] "); + arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information"); + arguments.getApplicationUsage()->addCommandLineOption("--skip-static","Skip rendering of static objects"); + arguments.getApplicationUsage()->addCommandLineOption("--skip-dynamic","Skip rendering of dynamic objects"); + arguments.getApplicationUsage()->addCommandLineOption("--export-objects","Export instance objects to files"); + arguments.getApplicationUsage()->addCommandLineOption("--use-multi-draw","Use glMultiDrawArraysIndirect in draw shader. Requires OpenGL version 4.3."); + arguments.getApplicationUsage()->addCommandLineOption("--instances-per-cell","How many static instances per cell = 4096"); + arguments.getApplicationUsage()->addCommandLineOption("--static-area-size","Size of the area for static rendering = 2000"); + arguments.getApplicationUsage()->addCommandLineOption("--dynamic-area-size","Size of the area for dynamic rendering = 1000"); + arguments.getApplicationUsage()->addCommandLineOption("--lod-modifier","LOD range [%] = 100"); + arguments.getApplicationUsage()->addCommandLineOption("--density-modifier","Instance density [%] = 100"); + arguments.getApplicationUsage()->addCommandLineOption("--triangle-modifier","Instance triangle quantity [%] = 100"); + + if (arguments.read("-h") || arguments.read("--help")) + { + arguments.getApplicationUsage()->write(std::cout); + return 1; + } + + // application configuration - default values + bool showStaticRendering = true; + bool showDynamicRendering = true; + bool exportInstanceObjects = false; + bool useMultiDrawArraysIndirect = false; + unsigned int instancesPerCell = 4096; + float staticAreaSize = 2000.0f; + float dynamicAreaSize = 1000.0f; + float lodModifier = 100.0f; + float densityModifier = 100.0f; + float triangleModifier = 100.0f; + + if ( arguments.read("--skip-static") ) + showStaticRendering = false; + if ( arguments.read("--skip-dynamic") ) + showDynamicRendering = false; + if ( arguments.read("--export-objects") ) + exportInstanceObjects = true; + if ( arguments.read("--use-multi-draw") ) + useMultiDrawArraysIndirect = true; + arguments.read("--instances-per-cell",instancesPerCell); + arguments.read("--static-area-size",staticAreaSize); + arguments.read("--dynamic-area-size",dynamicAreaSize); + arguments.read("--lod-modifier",lodModifier); + arguments.read("--density-modifier",densityModifier); + arguments.read("--triangle-modifier",triangleModifier); + + lodModifier /= 100.0f; + densityModifier /= 100.0f; + triangleModifier /= 100.0f; + + // construct the viewer. + osgViewer::Viewer viewer(arguments); + // To enable proper LOD fading we use multisampling + osg::DisplaySettings::instance()->setNumMultiSamples( 4 ); + + // Add basic event handlers and manipulators + viewer.addEventHandler(new osgViewer::StatsHandler); + viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet())); + viewer.addEventHandler(new osgViewer::ThreadingHandler); + + osg::ref_ptr keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator; + + keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() ); + keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() ); + keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() ); + keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() ); + viewer.setCameraManipulator( keyswitchManipulator.get() ); + + // create root node + osg::ref_ptr root = new osg::Group; + + // add lightsource to root node + osg::ref_ptr lSource = new osg::LightSource; + lSource->getLight()->setPosition(osg::Vec4(1.0,-1.0,1.0,0.0)); + lSource->getLight()->setDiffuse(osg::Vec4(0.9,0.9,0.9,1.0)); + lSource->getLight()->setAmbient(osg::Vec4(0.1,0.1,0.1,1.0)); + root->addChild( lSource.get() ); + + // Add ground + osg::ref_ptr groundGeometry = createTexturedQuadGeometry( osg::Vec3(-0.5*staticAreaSize,-0.5*staticAreaSize,0.0), osg::Vec3(0.0,staticAreaSize,0.0), osg::Vec3(staticAreaSize,0.0,0.0) ); + osg::ref_ptr groundGeode = new osg::Geode; + groundGeode->addDrawable( groundGeometry.get() ); + root->addChild( groundGeode.get() ); + + // Uniform for trees waving in the wind + osg::Vec2 windDirection( random(-1.0,1.0), random(-1.0,1.0) ); + windDirection.normalize(); + osg::ref_ptr windDirectionUniform = new osg::Uniform("windDirection", windDirection); + root->getOrCreateStateSet()->addUniform( windDirectionUniform.get() ); + + // create static objects and setup its rendering + GPUCullData staticGpuData; + staticGpuData.setUseMultiDrawArraysIndirect( useMultiDrawArraysIndirect ); + if(showStaticRendering) + { + createStaticRendering( root.get(), staticGpuData, osg::Vec2(-0.5*staticAreaSize,-0.5*staticAreaSize), osg::Vec2(0.5*staticAreaSize,0.5*staticAreaSize), instancesPerCell, lodModifier, densityModifier, triangleModifier, exportInstanceObjects ); + } + + // create dynamic objects and setup its rendering + GPUCullData dynamicGpuData; + dynamicGpuData.setUseMultiDrawArraysIndirect( useMultiDrawArraysIndirect ); + osg::ref_ptr< osg::BufferTemplate< std::vector< DynamicInstance> > > dynamicInstances; + if(showDynamicRendering) + { + dynamicInstances = new osg::BufferTemplate< std::vector >(); + createDynamicRendering( root.get(), dynamicGpuData, dynamicInstances.get(), osg::Vec2(-0.5*dynamicAreaSize,-0.5*dynamicAreaSize), osg::Vec2(0.5*dynamicAreaSize,0.5*dynamicAreaSize), lodModifier, densityModifier, triangleModifier, exportInstanceObjects ); + } + + viewer.setSceneData( root.get() ); + + viewer.realize(); + + // shaders use osg_ variables so we must do the following + osgViewer::Viewer::Windows windows; + viewer.getWindows(windows); + for(osgViewer::Viewer::Windows::iterator itr = windows.begin(); itr != windows.end(); ++itr) + (*itr)->getState()->setUseModelViewAndProjectionUniforms(true); + + return viewer.run(); +} diff --git a/include/osg/BufferTemplate b/include/osg/BufferTemplate new file mode 100644 index 000000000..3807fbb0b --- /dev/null +++ b/include/osg/BufferTemplate @@ -0,0 +1,104 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield + * Copyright (C) 2012 David Callu + * std::vector specialization : Pawel Ksiezopolski + * + * 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_BUFFERTEMPLATE +#define OSG_BUFFERTEMPLATE 1 + +#include +#include + +namespace osg +{ + +/** Template buffer class to be used with a struct as template parameter. + * This class is usefull to send C++ structures on the GPU (e.g. for uniform blocks) but be carefull to the alignments rules on the GPU side ! + */ +template +class BufferTemplate : public BufferData +{ + public: + BufferTemplate(): + BufferData(), + _data(T()) + {} + + /** Copy constructor using CopyOp to manage deep vs shallow copy.*/ + BufferTemplate(const BufferTemplate& bt,const CopyOp& copyop=CopyOp::SHALLOW_COPY): + osg::BufferData(bt,copyop), + _data(bt._data) + {} + + virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast*>(obj)!=NULL; } + virtual const char* libraryName() const { return "osg"; } + virtual const char* className() const { return "BufferTemplate"; } + + virtual Object* cloneType() const { return new BufferTemplate(); } + virtual Object* clone(const CopyOp& copyop) const { return new BufferTemplate(*this,copyop); } + + virtual const GLvoid* getDataPointer() const { return &_data; } + virtual unsigned int getTotalDataSize() const { return sizeof(T); } + + const T& getData() const { return _data; } + T& getData() { return _data; } + void setData(const T& data) { _data = data; dirty(); } + + protected: + virtual ~BufferTemplate() {}; + + private: + T _data; +}; + +/** BufferTemplate specialization for std::vector + */ +template +class BufferTemplate< std::vector > : public BufferData +{ + public: + BufferTemplate(): + BufferData(), + _data() + {} + + /** Copy constructor using CopyOp to manage deep vs shallow copy.*/ + BufferTemplate(const BufferTemplate< std::vector >& bt,const CopyOp& copyop=CopyOp::SHALLOW_COPY): + osg::BufferData(bt,copyop), + _data(bt._data) + {} + + virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast >*>(obj)!=NULL; } + virtual const char* libraryName() const { return "osg"; } + virtual const char* className() const { return "BufferTemplate >"; } + + virtual Object* cloneType() const { return new BufferTemplate< std::vector >(); } + virtual Object* clone(const CopyOp& copyop) const { return new BufferTemplate< std::vector >(*this,copyop); } + + virtual const GLvoid* getDataPointer() const { return &_data[0]; } + virtual unsigned int getTotalDataSize() const { return _data.size() * sizeof(T); } + + const std::vector& getData() const { return _data; } + std::vector& getData() { return _data; } + void setData(const std::vector& data) { _data = data; dirty(); } + + protected: + virtual ~BufferTemplate() {}; + + private: + std::vector _data; +}; + +} + +#endif diff --git a/src/osg/CMakeLists.txt b/src/osg/CMakeLists.txt index 4583193f4..0364b0293 100644 --- a/src/osg/CMakeLists.txt +++ b/src/osg/CMakeLists.txt @@ -37,7 +37,7 @@ SET(TARGET_H ${HEADER_PATH}/buffered_value ${HEADER_PATH}/BufferIndexBinding ${HEADER_PATH}/BufferObject - ${HEADER_PATH}/Callback + ${HEADER_PATH}/BufferTemplate ${HEADER_PATH}/Camera ${HEADER_PATH}/CameraView ${HEADER_PATH}/ClampColor @@ -72,6 +72,7 @@ SET(TARGET_H ${HEADER_PATH}/FrontFace ${HEADER_PATH}/Geode ${HEADER_PATH}/Geometry + ${HEADER_PATH}/GL ${HEADER_PATH}/GL2Extensions ${HEADER_PATH}/GLExtensions ${HEADER_PATH}/GLBeginEndAdapter