Further work on TextureAlasBuilder and TextureAtlasVisitor.

This commit is contained in:
Robert Osfield
2006-08-28 10:46:39 +00:00
parent 9f6be131a4
commit ee7f3fa375
2 changed files with 251 additions and 11 deletions

View File

@@ -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<osg::StateSet*> StateSets;
typedef std::set<osg::Texture2D*> Textures;
TextureAtlasBuilder _builder;
StateSets _statesets;
Textures _textures;
};
};
inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::StateSet* object) const

View File

@@ -27,6 +27,7 @@
#include <osg/PagedLOD>
#include <osg/ProxyNode>
#include <osg/Timer>
#include <osg/TexMat>
#include <osg/io_utils>
#include <osgUtil/TransformAttributeFunctor>
@@ -37,6 +38,7 @@
#include <typeinfo>
#include <algorithm>
#include <numeric>
#include <sstream>
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"<<std::endl;
// traverse the scene collecting textures into texture atlas.
TextureAtlasVisitor tav(this);
node->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"<<std::endl;
@@ -2956,12 +2975,18 @@ void Optimizer::FlattenBillboardVisitor::process()
////////////////////////////////////////////////////////////////////////////
Optimizer::TextureAtlasBuilder::TextureAtlasBuilder():
_maximumAtlasWidth(2048),
_maximumAtlasHeight(2048),
_margin(16)
_maximumAtlasWidth(512),
_maximumAtlasHeight(512),
_margin(8)
{
}
void Optimizer::TextureAtlasBuilder::reset()
{
_sourceList.clear();
_atlasList.clear();
}
void Optimizer::TextureAtlasBuilder::setMaximumAtlasSize(unsigned int width, unsigned int height)
{
_maximumAtlasWidth = width;
@@ -2996,17 +3021,25 @@ void Optimizer::TextureAtlasBuilder::buildAtlas()
{
bool addedSourceToAtlas = false;
for(AtlasList::iterator aitr = _atlasList.begin();
aitr != _atlasList.end() || addedSourceToAtlas;
aitr != _atlasList.end() && !addedSourceToAtlas;
++aitr)
{
osg::notify(osg::NOTICE)<<"checking source "<<source->_image->getFileName()<<" to see it it'll fit in atlas "<<aitr->get()<<std::endl;
if ((*aitr)->doesSourceFit(source))
{
addedSourceToAtlas = true;
(*aitr)->addSource(source);
}
else
{
osg::notify(osg::NOTICE)<<"source "<<source->_image->getFileName()<<" does not fit in atlas "<<aitr->get()<<std::endl;
}
}
if (!addedSourceToAtlas)
{
osg::notify(osg::NOTICE)<<"creating new Atlas for "<<source->_image->getFileName()<<std::endl;
osg::ref_ptr<Atlas> 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_"<<activeAtlasList.size()<<".rgb";
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"<<std::endl;
return true;
}
@@ -3301,6 +3340,7 @@ bool Optimizer::TextureAtlasBuilder::Atlas::doesSourceFit(Source* source)
if ((_height + sourceImage->t() + 2*_margin) <= _maximumAtlasHeight)
{
// yes it fits :-)
osg::notify(osg::NOTICE)<<"Fits in next row"<<std::endl;
return true;
}
@@ -3311,7 +3351,11 @@ bool Optimizer::TextureAtlasBuilder::Atlas::doesSourceFit(Source* source)
bool Optimizer::TextureAtlasBuilder::Atlas::addSource(Source* source)
{
// double check source is compatible
if (!doesSourceFit(source)) return false;
if (!doesSourceFit(source))
{
osg::notify(osg::NOTICE)<<"source "<<source->_image->getFileName()<<" does not fit in atlas "<<this<<std::endl;
return false;
}
const osg::Image* sourceImage = source->_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 "<<source->_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<<this<<std::endl;
// set up the source so it knows where it is in the atlas
source->_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 "<<source->_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<<this<<std::endl;
// set up the source so it knows where it is in the atlas
source->_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 "<<source->_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<<this<<std::endl;
return true;
}
osg::notify(osg::NOTICE)<<"source "<<source->_image->getFileName()<<" does not fit in atlas "<<this<<std::endl;
// shouldn't get here, unless doesSourceFit isn't working...
return false;
}
@@ -3405,8 +3464,17 @@ void Optimizer::TextureAtlasBuilder::Atlas::clampToNearestPowerOfTwoSize()
_height = w;
}
#if 0
#include <osgDB/WriteFile>
#endif
void Optimizer::TextureAtlasBuilder::Atlas::copySources()
{
_image->allocateImage(_width,_height,1,
_image->getPixelFormat(),_image->getDataType(),
_image->getPacking());
osg::notify(osg::NOTICE)<<"Atlas::copySources() "<<std::endl;
for(SourceList::iterator itr = _sourceList.begin();
itr !=_sourceList.end();
@@ -3422,7 +3490,7 @@ void Optimizer::TextureAtlasBuilder::Atlas::copySources()
const osg::Image* sourceImage = source->_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; t<sourceImage->t(); ++t, ++y)
@@ -3436,5 +3504,134 @@ void Optimizer::TextureAtlasBuilder::Atlas::copySources()
}
}
}
#if 0
osg::notify(osg::NOTICE)<<"Writing atlas image "<<_image->getFileName()<<std::endl;
osgDB::writeImageFile(*_image,_image->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<tal.size(); ++unit)
{
osg::Texture2D* texture2D = dynamic_cast<osg::Texture2D*>(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;i<geode.getNumDrawables();++i)
{
osg::Drawable* drawable = geode.getDrawable(i);
if (drawable)
{
ss = drawable->getStateSet();
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<tal.size(); ++unit)
{
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(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)));
}
}
}
}
}