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