diff --git a/include/osgUtil/Optimizer b/include/osgUtil/Optimizer index 5eab6de0d..f3707bbcd 100644 --- a/include/osgUtil/Optimizer +++ b/include/osgUtil/Optimizer @@ -81,6 +81,7 @@ class OSGUTIL_EXPORT Optimizer OPTIMIZE_TEXTURE_SETTINGS = 0x800, MERGE_GEODES = 0x1000, FLATTEN_BILLBOARDS = 0x2000, + TEXTURE_ATLAS_BUILDER = 0x4000, DEFAULT_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS | REMOVE_REDUNDANT_NODES | REMOVE_LOADED_PROXY_NODES | @@ -100,7 +101,8 @@ class OSGUTIL_EXPORT Optimizer SPATIALIZE_GROUPS | COPY_SHARED_NODES | TRISTRIP_GEOMETRY | - OPTIMIZE_TEXTURE_SETTINGS + OPTIMIZE_TEXTURE_SETTINGS | + TEXTURE_ATLAS_BUILDER }; /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ @@ -578,6 +580,8 @@ class OSGUTIL_EXPORT Optimizer public: TextureAtlasBuilder(); + void reset(); + void setMaximumAtlasSize(unsigned int width, unsigned int height); unsigned int getMaximumAtlasWidth() const { return _maximumAtlasWidth; } @@ -603,7 +607,7 @@ class OSGUTIL_EXPORT Optimizer osg::Texture2D* getTextureAtlas(const osg::Image* image); osg::Matrix getTextureMatrix(const osg::Image* image); - osg::Image* getImageAtlas(const osg::Texture2D* image); + osg::Image* getImageAtlas(const osg::Texture2D* textue); osg::Texture2D* getTextureAtlas(const osg::Texture2D* texture); osg::Matrix getTextureMatrix(const osg::Texture2D* texture); @@ -655,6 +659,7 @@ class OSGUTIL_EXPORT Optimizer _maximumAtlasHeight(height), _margin(margin), _x(0), + _y(0), _width(0), _height(0){} @@ -691,6 +696,44 @@ class OSGUTIL_EXPORT Optimizer AtlasList _atlasList; }; + + /** Optimize texture usage in the scene graph by combining textures into texture atlas + * Use of texture atlas cuts down on the number of seperate states in the scene, reducing + * state changes and improving the chances of use larger batches of geomertry.*/ + class OSGUTIL_EXPORT TextureAtlasVisitor : public BaseOptimizerVisitor + { + public: + + /// default to traversing all children. + TextureAtlasVisitor(Optimizer* optimizer=0): + BaseOptimizerVisitor(optimizer, TEXTURE_ATLAS_BUILDER) {} + + + TextureAtlasBuilder& getTextureAtlasBuilder() { return _builder; } + + /** empty visitor, make it ready for next traversal.*/ + virtual void reset(); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Geode& geode); + + void optimize(); + + protected: + + void addStateSet(osg::StateSet* stateset); + + typedef std::set StateSets; + typedef std::set Textures; + + TextureAtlasBuilder _builder; + + StateSets _statesets; + Textures _textures; + + }; + }; inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::StateSet* object) const diff --git a/src/osgUtil/Optimizer.cpp b/src/osgUtil/Optimizer.cpp index b70d1bb0e..5817c2c41 100644 --- a/src/osgUtil/Optimizer.cpp +++ b/src/osgUtil/Optimizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include using namespace osgUtil; @@ -104,6 +106,8 @@ void Optimizer::optimize(osg::Node* node) if(str.find("~FLATTEN_BILLBOARDS")!=std::string::npos) options ^= FLATTEN_BILLBOARDS; else if(str.find("FLATTEN_BILLBOARDS")!=std::string::npos) options |= FLATTEN_BILLBOARDS; + if(str.find("~TEXTURE_ATLAS_BUILDER")!=std::string::npos) options ^= TEXTURE_ATLAS_BUILDER; + else if(str.find("TEXTURE_ATLAS_BUILDER")!=std::string::npos) options |= TEXTURE_ATLAS_BUILDER; } else @@ -174,6 +178,21 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) osv.optimize(); } + if (options & TEXTURE_ATLAS_BUILDER) + { + osg::notify(osg::INFO)<<"Optimizer::optimize() doing TEXTURE_ATLAS_BUILDER"<accept(tav); + tav.optimize(); + + // now merge duplicate state, that may have been introduced by merge textures into texture atlas' + StateVisitor osv(this); + node->accept(osv); + osv.optimize(); + } + if (options & COPY_SHARED_NODES) { osg::notify(osg::INFO)<<"Optimizer::optimize() doing COPY_SHARED_NODES"<_image->getFileName()<<" to see it it'll fit in atlas "<get()<doesSourceFit(source)) { + addedSourceToAtlas = true; (*aitr)->addSource(source); } + else + { + osg::notify(osg::NOTICE)<<"source "<_image->getFileName()<<" does not fit in atlas "<get()<_image->getFileName()< atlas = new Atlas(_maximumAtlasWidth,_maximumAtlasHeight,_margin); _atlasList.push_back(atlas.get()); @@ -3027,16 +3060,21 @@ void Optimizer::TextureAtlasBuilder::buildAtlas() { // no point building an atlas with only one entry // so disconnect the source. - Source* source = atlas->_sourceList[1].get(); + Source* source = atlas->_sourceList[0].get(); source->_atlas = 0; atlas->_sourceList.clear(); } if (!(atlas->_sourceList.empty())) { + std::stringstream ostr; + ostr<<"atlas_"<_image->setFileName(ostr.str()); + activeAtlasList.push_back(atlas); atlas->clampToNearestPowerOfTwoSize(); atlas->copySources(); + } } @@ -3169,7 +3207,7 @@ osg::Matrix Optimizer::TextureAtlasBuilder::Source::computeTextureMatrix() const if (!(_atlas->_image)) return osg::Matrix(); return osg::Matrix::scale(float(_image->s())/float(_atlas->_image->s()), float(_image->t())/float(_atlas->_image->t()), 1.0)* - osg::Matrix::translate(float(_x)/float(_atlas->_image->s()), float(_y)/float(_atlas->_image->t()), 1.0); + osg::Matrix::translate(float(_x)/float(_atlas->_image->s()), float(_y)/float(_atlas->_image->t()), 0.0); } bool Optimizer::TextureAtlasBuilder::Atlas::doesSourceFit(Source* source) @@ -3294,6 +3332,7 @@ bool Optimizer::TextureAtlasBuilder::Atlas::doesSourceFit(Source* source) if ((_x + sourceImage->s() + 2*_margin) <= _maximumAtlasWidth) { // yes it fits :-) + osg::notify(osg::NOTICE)<<"Fits in current row"<t() + 2*_margin) <= _maximumAtlasHeight) { // yes it fits :-) + osg::notify(osg::NOTICE)<<"Fits in next row"<_image->getFileName()<<" does not fit in atlas "<_image.get(); const osg::Texture2D* sourceTexture = source->_texture.get(); @@ -3353,6 +3397,8 @@ bool Optimizer::TextureAtlasBuilder::Atlas::addSource(Source* source) // yes it fits, so add the source to the atlas's list of sources it contains _sourceList.push_back(source); + osg::notify(osg::NOTICE)<<"current row insertion, source "<_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<_x = _x + _margin; source->_y = _y + _margin; @@ -3363,6 +3409,9 @@ bool Optimizer::TextureAtlasBuilder::Atlas::addSource(Source* source) if (_x > _width) _width = _x; + unsigned int localTop = _y + sourceImage->t() + 2*_margin; + if ( localTop > _height) _height = localTop; + return true; } @@ -3376,6 +3425,8 @@ bool Optimizer::TextureAtlasBuilder::Atlas::addSource(Source* source) // yes it fits, so add the source to the atlas' list of sources it contains _sourceList.push_back(source); + osg::notify(osg::NOTICE)<<"next row insertion, source "<_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<_x = _x + _margin; source->_y = _y + _margin; @@ -3383,10 +3434,18 @@ bool Optimizer::TextureAtlasBuilder::Atlas::addSource(Source* source) // move the atlas' cursor along to the right _x += sourceImage->s() + 2*_margin; - + + if (_x > _width) _width = _x; + + _height = _y + sourceImage->t() + 2*_margin; + + osg::notify(osg::NOTICE)<<"source "<_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<_image->getFileName()<<" does not fit in atlas "< +#endif + void Optimizer::TextureAtlasBuilder::Atlas::copySources() { + _image->allocateImage(_width,_height,1, + _image->getPixelFormat(),_image->getDataType(), + _image->getPacking()); + + osg::notify(osg::NOTICE)<<"Atlas::copySources() "<_image.get(); osg::Image* atlasImage = atlas->_image.get(); - unsigned int x = source->_y; + unsigned int x = source->_x; unsigned int y = source->_y; unsigned int rowWidth = sourceImage->getRowSizeInBytes(); for(int t=0; tt(); ++t, ++y) @@ -3436,5 +3504,134 @@ void Optimizer::TextureAtlasBuilder::Atlas::copySources() } } } +#if 0 + osg::notify(osg::NOTICE)<<"Writing atlas image "<<_image->getFileName()<getFileName()); +#endif +} + + +void Optimizer::TextureAtlasVisitor::reset() +{ + _statesets.clear(); + _textures.clear(); + _builder.reset(); +} + +void Optimizer::TextureAtlasVisitor::addStateSet(osg::StateSet* stateset) +{ + osg::StateSet::TextureAttributeList& tal = stateset->getTextureAttributeList(); + + // if no textures ignore + if (tal.empty()) return; + + // if already in stateset list ignore + if (_statesets.count(stateset)>0) return; + + bool containsTexture2D = false; + for(unsigned int unit=0; unit(stateset->getTextureAttribute(unit,osg::StateAttribute::TEXTURE)); + if (texture2D) + { + containsTexture2D = true; + _textures.insert(texture2D); + } + } + + if (containsTexture2D) + { + _statesets.insert(stateset); + } + +} + +void Optimizer::TextureAtlasVisitor::apply(osg::Node& node) +{ + + osg::StateSet* ss = node.getStateSet(); + if (ss && ss->getDataVariance()==osg::Object::STATIC) + { + if (isOperationPermissibleForObject(&node) && + isOperationPermissibleForObject(ss)) + { + addStateSet(ss); + } + } + + traverse(node); +} + +void Optimizer::TextureAtlasVisitor::apply(osg::Geode& geode) +{ + if (!isOperationPermissibleForObject(&geode)) return; + + osg::StateSet* ss = geode.getStateSet(); + + + if (ss && ss->getDataVariance()==osg::Object::STATIC) + { + if (isOperationPermissibleForObject(ss)) + { + addStateSet(ss); + } + } + for(unsigned int i=0;igetStateSet(); + if (ss && ss->getDataVariance()==osg::Object::STATIC) + { + if (isOperationPermissibleForObject(drawable) && + isOperationPermissibleForObject(ss)) + { + addStateSet(ss); + } + } + } + } +} + +void Optimizer::TextureAtlasVisitor::optimize() +{ + osg::notify(osg::INFO) << "Num of StateSet="<<_statesets.size()<< std::endl; + + _builder.reset(); + + // add the textures as sources for the TextureAtlasBuilder + for(Textures::iterator titr = _textures.begin(); + titr != _textures.end(); + ++titr) + { + _builder.addSource(*titr); + } + + // build the atlas' + _builder.buildAtlas(); + + // remap the textures in the StateSet's + for(StateSets::iterator sitr = _statesets.begin(); + sitr != _statesets.end(); + ++sitr) + { + osg::StateSet* stateset = *sitr; + osg::StateSet::TextureAttributeList& tal = stateset->getTextureAttributeList(); + for(unsigned int unit=0; unit(stateset->getTextureAttribute(unit,osg::StateAttribute::TEXTURE)); + if (texture) + { + osg::Texture2D* newTexture = _builder.getTextureAtlas(texture); + if (newTexture && newTexture!=texture) + { + stateset->setTextureAttribute(unit, newTexture); + stateset->setTextureAttribute(unit, new osg::TexMat(_builder.getTextureMatrix(texture))); + } + } + } + + } }