rewrite ModelRegistry callbacks as a template with pluggable policy classes

In a big effort to improve use of the object cache, provide a
ModelRegistryCallback template class with different policies for
substitution,  caching, optimization, etc.

Change SGTexDataVarianceVistor to make StateSets static too.
This commit is contained in:
timoore 2007-11-29 23:56:09 +00:00
parent f182886fce
commit 4a959ec2fd
3 changed files with 336 additions and 125 deletions

View File

@ -1,3 +1,21 @@
// ModelRegistry.hxx -- interface to the OSG model registry
//
// Copyright (C) 2005-2007 Mathias Froehlich
// Copyright (C) 2007 Tim Moore <timoore@redhat.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program 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 GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "ModelRegistry.hxx"
#include <osg/observer_ptr>
@ -27,6 +45,7 @@
using namespace std;
using namespace osg;
using namespace osgUtil;
using namespace osgDB;
using namespace simgear;
@ -156,6 +175,14 @@ public:
texture->setDataVariance(Object::STATIC);
}
virtual void apply(StateSet* stateSet)
{
if (!stateSet)
return;
SGTextureStateAttributeVisitor::apply(stateSet);
stateSet->setDataVariance(Object::STATIC);
}
};
class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
@ -199,154 +226,108 @@ ModelRegistry::readImage(const string& fileName,
return res;
}
ReaderWriter::ReadResult
ModelRegistry::readNode(const string& fileName,
const ReaderWriter::Options* opt)
osg::Node* DefaultCachePolicy::find(const string& fileName,
const ReaderWriter::Options* opt)
{
Registry* registry = Registry::instance();
ReaderWriter::ReadResult res;
Node* cached = 0;
CallbackMap::iterator iter
= nodeCallbackMap.find(getFileExtension(fileName));
if (iter != nodeCallbackMap.end() && iter->second.valid())
return iter->second->readNode(fileName, opt);
// First, look for a file with the same name, and the extension
// ".osg" and, if it exists, load it instead. This allows for
// substitution of optimized models for ones named in the scenery.
bool optimizeModel = true;
string fileSansExtension = getNameLessExtension(fileName);
string osgFileName = fileSansExtension + ".osg";
string absFileName = findDataFile(osgFileName);
// The absolute file name is passed to the reader plugin, which
// calls findDataFile again... but that's OK, it should find the
// file by its absolute path straight away.
if (fileExists(absFileName)) {
optimizeModel = false;
} else {
absFileName = findDataFile(fileName);
}
if (!fileExists(absFileName)) {
SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
<< fileName << "\"");
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
}
cached
= dynamic_cast<Node*>(registry->getFromObjectCache(absFileName));
if (cached) {
osg::Node* cached
= dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
if (cached)
SG_LOG(SG_IO, SG_INFO, "Got cached model \""
<< absFileName << "\"");
} else {
<< fileName << "\"");
else
SG_LOG(SG_IO, SG_INFO, "Reading model \""
<< absFileName << "\"");
res = registry->readNodeImplementation(absFileName, opt);
if (!res.validNode())
return res;
<< fileName << "\"");
return cached;
}
bool needTristrip = true;
if (getLowerCaseFileExtension(fileName) == "ac") {
// we get optimal geometry from the loader.
needTristrip = false;
Matrix m(1, 0, 0, 0,
0, 0, 1, 0,
0, -1, 0, 0,
0, 0, 0, 1);
ref_ptr<Group> root = new Group;
MatrixTransform* transform = new MatrixTransform;
root->addChild(transform);
transform->setDataVariance(Object::STATIC);
transform->setMatrix(m);
transform->addChild(res.getNode());
res = ReaderWriter::ReadResult(0);
void DefaultCachePolicy::addToCache(const string& fileName,
osg::Node* node)
{
Registry::instance()->addEntryToObjectCache(fileName, node);
}
if (optimizeModel) {
osgUtil::Optimizer optimizer;
unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
optimizer.optimize(root.get(), opts);
}
// Optimizations we don't use:
// Don't use this one. It will break animation names ...
// opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
//
// opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
// opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
// opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
// opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
// opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
// opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
// opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
// strip away unneeded groups
if (root->getNumChildren() == 1 && root->getName().empty()) {
res = ReaderWriter::ReadResult(root->getChild(0));
} else
res = ReaderWriter::ReadResult(root.get());
// Ok, this step is questionable.
// It is there to have the same visual appearance of ac objects for the
// first cut. Osg's ac3d loader will correctly set materials from the
// ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
// materials that in effect igored the ambient part specified in the
// file. We emulate that for the first cut here by changing all
// ac models here. But in the long term we should use the
// unchanged model and fix the input files instead ...
SGAcMaterialCrippleVisitor matCriple;
res.getNode()->accept(matCriple);
}
OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
_osgOptions(Optimizer::SHARE_DUPLICATE_STATE
| Optimizer::MERGE_GEOMETRY
| Optimizer::FLATTEN_STATIC_TRANSFORMS
| Optimizer::TRISTRIP_GEOMETRY)
{
}
if (optimizeModel) {
osgUtil::Optimizer optimizer;
unsigned opts = 0;
// Don't use this one. It will break animation names ...
// opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
const string& fileName,
const osgDB::ReaderWriter::Options* opt)
{
osgUtil::Optimizer optimizer;
optimizer.optimize(node, _osgOptions);
// opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
// opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
// opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
// opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
// opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
// opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
opts |= osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
if (needTristrip)
opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
// opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
// opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
optimizer.optimize(res.getNode(), opts);
}
// Make sure the data variance of sharable objects is set to STATIC ...
SGTexDataVarianceVisitor dataVarianceVisitor;
res.getNode()->accept(dataVarianceVisitor);
// ... so that textures are now globally shared
registry->getSharedStateManager()->share(res.getNode());
// Make sure the data variance of sharable objects is set to
// STATIC so that textures will be globally shared.
SGTexDataVarianceVisitor dataVarianceVisitor;
node->accept(dataVarianceVisitor);
SGTexCompressionVisitor texComp;
res.getNode()->accept(texComp);
cached = res.getNode();
registry->addEntryToObjectCache(absFileName, cached);
}
SGTexCompressionVisitor texComp;
node->accept(texComp);
return node;
}
osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
const osgDB::ReaderWriter::Options* opt)
{
// Add an extra reference to the model stored in the database.
// That it to avoid expiring the object from the cache even if it is still
// in use. Note that the object cache will think that a model is unused
// if the reference count is 1. If we clone all structural nodes here
// we need that extra reference to the original object
SGDatabaseReference* databaseReference;
databaseReference = new SGDatabaseReference(cached);
databaseReference = new SGDatabaseReference(model);
CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
flags &= ~CopyOp::DEEP_COPY_TEXTURES;
flags &= ~CopyOp::DEEP_COPY_IMAGES;
flags &= ~CopyOp::DEEP_COPY_STATESETS;
flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
flags &= ~CopyOp::DEEP_COPY_ARRAYS;
flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
// This will safe display lists ...
flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
flags &= ~CopyOp::DEEP_COPY_SHAPES;
res = ReaderWriter::ReadResult(CopyOp(flags)(cached));
res.getNode()->addObserver(databaseReference);
osg::Node* res = CopyOp(flags)(model);
res->addObserver(databaseReference);
// Update liveries
SGTextureUpdateVisitor liveryUpdate(getDataFilePathList());
res.getNode()->accept(liveryUpdate);
// Make sure the data variance of sharable objects is set to STATIC ...
SGTexDataVarianceVisitor dataVarianceVisitor;
res.getNode()->accept(dataVarianceVisitor);
// ... so that textures are now globally shared
registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
res->accept(liveryUpdate);
return res;
}
string OSGSubstitutePolicy::substitute(const string& name,
const ReaderWriter::Options* opt)
{
string fileSansExtension = getNameLessExtension(name);
string osgFileName = fileSansExtension + ".osg";
string absFileName = findDataFile(osgFileName);
return absFileName;
}
ModelRegistry::ModelRegistry() :
_defaultCallback(new DefaultCallback(""))
{
}
void
ModelRegistry::addImageCallbackForExtension(const string& extension,
Registry::ReadFileCallback* callback)
@ -371,6 +352,20 @@ ModelRegistry* ModelRegistry::getInstance()
return instance.get();
}
ReaderWriter::ReadResult
ModelRegistry::readNode(const string& fileName,
const ReaderWriter::Options* opt)
{
Registry* registry = Registry::instance();
ReaderWriter::ReadResult res;
Node* cached = 0;
CallbackMap::iterator iter
= nodeCallbackMap.find(getFileExtension(fileName));
if (iter != nodeCallbackMap.end() && iter->second.valid())
return iter->second->readNode(fileName, opt);
return _defaultCallback->readNode(fileName, opt);
}
class SGReadCallbackInstaller {
public:
SGReadCallbackInstaller()
@ -381,20 +376,69 @@ public:
Registry* registry = Registry::instance();
ReaderWriter::Options* options = new ReaderWriter::Options;
// We manage node caching ourselves
int cacheOptions = ReaderWriter::Options::CACHE_ALL
& ~ReaderWriter::Options::CACHE_NODES;
int cacheOptions = ReaderWriter::Options::CACHE_ALL;
options->
setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
registry->setOptions(options);
registry->getOrCreateSharedStateManager()->
setShareMode(SharedStateManager::SHARE_TEXTURES);
setShareMode(SharedStateManager::SHARE_STATESETS);
registry->setReadFileCallback(ModelRegistry::getInstance());
}
};
static SGReadCallbackInstaller readCallbackInstaller;
// we get optimal geometry from the loader.
struct ACOptimizePolicy : public OptimizeModelPolicy {
ACOptimizePolicy(const string& extension) :
OptimizeModelPolicy(extension)
{
_osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
}
};
struct ACProcessPolicy {
ACProcessPolicy(const string& extension) {}
Node* process(Node* node, const string& filename,
const ReaderWriter::Options* opt)
{
Matrix m(1, 0, 0, 0,
0, 0, 1, 0,
0, -1, 0, 0,
0, 0, 0, 1);
// XXX Does there need to be a Group node here to trick the
// optimizer into optimizing the static transform?
osg::Group* root = new Group;
MatrixTransform* transform = new MatrixTransform;
root->addChild(transform);
transform->setDataVariance(Object::STATIC);
transform->setMatrix(m);
transform->addChild(node);
// Ok, this step is questionable.
// It is there to have the same visual appearance of ac objects for the
// first cut. Osg's ac3d loader will correctly set materials from the
// ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
// materials that in effect igored the ambient part specified in the
// file. We emulate that for the first cut here by changing all
// ac models here. But in the long term we should use the
// unchanged model and fix the input files instead ...
SGAcMaterialCrippleVisitor matCriple;
root->accept(matCriple);
return root;
}
};
typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
ACOptimizePolicy, DefaultCopyPolicy,
OSGSubstitutePolicy> ACCallback;
namespace
{
ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
}
ReaderWriter::ReadResult
OSGFileCallback::readImage(const string& fileName,
const ReaderWriter::Options* opt)

View File

@ -1,5 +1,6 @@
// ModelRegistry.hxx -- interface to the OSG model registry
//
// Copyright (C) 2005-2007 Mathias Froehlich
// Copyright (C) 2007 Tim Moore <timoore@redhat.com>
//
// This program is free software; you can redistribute it and/or
@ -19,6 +20,9 @@
#define _SG_MODELREGISTRY_HXX 1
#include <osg/ref_ptr>
#include <osg/Node>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include <osgDB/ReaderWriter>
#include <osgDB/Registry>
@ -27,10 +31,165 @@
#include STL_STRING
#include <map>
// Class to register per file extension read callbacks with the OSG
// registry, mostly to control caching and post load optimization /
// copying that happens above the level of the ReaderWriter.
namespace simgear
{
// Different caching and optimization strategies are needed for
// different file types. Most loaded files should be optimized and the
// optimized version should be cached. When an .osg file is
// substituted for another, it is assumed to be optimized already but
// it should be cached too (under the name of the original?). .stg
// files should not be cached (that's the pager's job) but the files
// it causes to be loaded should be. .btg files are already optimized
// and shouldn't be cached.
//
// Complicating this is the effect that removing CACHE_NODES has from
// the ReaderWriter options: it switches the object cache with an
// empty one, so that's not an option for the files that could be
// loaded from a .stg file. So, we'll let
// Registry::readNodeImplementation cache a loaded file and then add
// the optimized version to the cache ourselves, replacing the
// original subgraph.
//
// To support all these options with a minimum of duplication, the
// readNode function is specified as a template with a bunch of
// pluggable (and predefined) policies.
template <typename ProcessPolicy, typename CachePolicy, typename OptimizePolicy,
typename CopyPolicy, typename SubstitutePolicy>
class ModelRegistryCallback : public osgDB::Registry::ReadFileCallback {
public:
ModelRegistryCallback(const std::string& extension) :
_processPolicy(extension), _cachePolicy(extension),
_optimizePolicy(extension), _copyPolicy(extension),
_substitutePolicy(extension)
{
}
virtual osgDB::ReaderWriter::ReadResult
readNode(const std::string& fileName,
const osgDB::ReaderWriter::Options* opt)
{
using namespace osg;
using namespace osgDB;
using osgDB::ReaderWriter;
Registry* registry = Registry::instance();
std::string usedFileName = _substitutePolicy.substitute(fileName, opt);
if (usedFileName.empty())
usedFileName = fileName;
ref_ptr<osg::Node> loadedNode = _cachePolicy.find(usedFileName, opt);
if (!loadedNode.valid()) {
ReaderWriter* rw = registry ->getReaderWriterForExtension(osgDB::getFileExtension(usedFileName));
if (!rw)
return ReaderWriter::ReadResult(); // FILE_NOT_HANDLED
ReaderWriter::ReadResult res = rw->readNode(usedFileName, opt);
if (!res.validNode())
return res;
ref_ptr<osg::Node> processedNode
= _processPolicy.process(res.getNode(), usedFileName, opt);
ref_ptr<osg::Node> optimizedNode
= _optimizePolicy.optimize(processedNode.get(), usedFileName,
opt);
_cachePolicy.addToCache(usedFileName, optimizedNode.get());
loadedNode = optimizedNode;
}
return ReaderWriter::ReadResult(_copyPolicy.copy(loadedNode.get(),
usedFileName,
opt));
}
protected:
ProcessPolicy _processPolicy;
CachePolicy _cachePolicy;
OptimizePolicy _optimizePolicy;
CopyPolicy _copyPolicy;
SubstitutePolicy _substitutePolicy;
virtual ~ModelRegistryCallback() {}
};
// Predefined policies
struct DefaultProcessPolicy {
DefaultProcessPolicy(const std::string& extension) {}
osg::Node* process(osg::Node* node, const std::string& filename,
const osgDB::ReaderWriter::Options* opt)
{
return node;
}
};
struct DefaultCachePolicy {
DefaultCachePolicy(const std::string& extension) {}
osg::Node* find(const std::string& fileName,
const osgDB::ReaderWriter::Options* opt);
void addToCache(const std::string& filename, osg::Node* node);
};
struct NoCachePolicy {
NoCachePolicy(const std::string& extension) {}
osg::Node* find(const std::string& fileName,
const osgDB::ReaderWriter::Options* opt)
{
return 0;
}
void addToCache(const std::string& filename, osg::Node* node) {}
};
class OptimizeModelPolicy {
public:
OptimizeModelPolicy(const std::string& extension);
osg::Node* optimize(osg::Node* node, const std::string& fileName,
const osgDB::ReaderWriter::Options* opt);
protected:
unsigned _osgOptions;
};
struct NoOptimizePolicy {
NoOptimizePolicy(const std::string& extension) {}
osg::Node* optimize(osg::Node* node, const std::string& fileName,
const osgDB::ReaderWriter::Options* opt)
{
return node;
}
};
struct DefaultCopyPolicy {
DefaultCopyPolicy(const std::string& extension) {}
osg::Node* copy(osg::Node* node, const std::string& fileName,
const osgDB::ReaderWriter::Options* opt);
};
struct NoCopyPolicy {
NoCopyPolicy(const std::string& extension) {}
osg::Node* copy(osg::Node* node, const std::string& fileName,
const osgDB::ReaderWriter::Options* opt)
{
return node;
}
};
struct OSGSubstitutePolicy {
OSGSubstitutePolicy(const std::string& extension) {}
std::string substitute(const std::string& name,
const osgDB::ReaderWriter::Options* opt);
};
struct NoSubstitutePolicy {
NoSubstitutePolicy(const std::string& extension) {}
std::string substitute(const std::string& name,
const osgDB::ReaderWriter::Options* opt)
{
return std::string();
}
};
typedef ModelRegistryCallback<DefaultProcessPolicy, DefaultCachePolicy,
OptimizeModelPolicy, DefaultCopyPolicy,
OSGSubstitutePolicy> DefaultCallback;
// The manager for the callbacks
class ModelRegistry : public osgDB::Registry::ReadFileCallback {
public:
ModelRegistry();
virtual osgDB::ReaderWriter::ReadResult
readImage(const std::string& fileName,
const osgDB::ReaderWriter::Options* opt);
@ -44,14 +203,22 @@ public:
osgDB::Registry::ReadFileCallback*
callback);
static ModelRegistry* getInstance();
virtual ~ModelRegistry() {}
protected:
static osg::ref_ptr<ModelRegistry> instance;
typedef std::map<std::string, osg::ref_ptr<osgDB::Registry::ReadFileCallback> >
CallbackMap;
CallbackMap imageCallbackMap;
CallbackMap nodeCallbackMap;
osg::ref_ptr<DefaultCallback> _defaultCallback;
};
// Callback that only loads the file without any caching or
// postprocessing.
typedef ModelRegistryCallback<DefaultProcessPolicy, NoCachePolicy,
NoOptimizePolicy, NoCopyPolicy,
NoSubstitutePolicy> LoadOnlyCallback;
// Proxy for registering extension-based callbacks
template<typename T>
@ -60,8 +227,8 @@ class ModelRegistryCallbackProxy
public:
ModelRegistryCallbackProxy(std::string extension)
{
ModelRegistry::getInstance()->addNodeCallbackForExtension(extension,
new T);
ModelRegistry::getInstance()
->addNodeCallbackForExtension(extension, new T(extension));
}
};

View File

@ -71,5 +71,5 @@ SGReaderWriterBTG::readNode(const std::string& fileName,
namespace
{
ModelRegistryCallbackProxy<OSGFileCallback> g_btgCallbackProxy("btg");
ModelRegistryCallbackProxy<LoadOnlyCallback> g_btgCallbackProxy("btg");
}