Merge remote-tracking branch 'scrawl/resource'
commit
da6dcfc49e
@ -0,0 +1,230 @@
|
||||
#include "cellpreloader.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/resource/keyframemanager.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
#include <components/terrain/world.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "cellstore.hpp"
|
||||
#include "manualref.hpp"
|
||||
#include "class.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
||||
struct ListModelsVisitor
|
||||
{
|
||||
ListModelsVisitor(std::vector<std::string>& out)
|
||||
: mOut(out)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
ptr.getClass().getModelsToPreload(ptr, mOut);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string>& mOut;
|
||||
};
|
||||
|
||||
/// Worker thread item: preload models in a cell.
|
||||
class PreloadItem : public SceneUtil::WorkItem
|
||||
{
|
||||
public:
|
||||
/// Constructor to be called from the main thread.
|
||||
PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain)
|
||||
: mIsExterior(cell->getCell()->isExterior())
|
||||
, mX(cell->getCell()->getGridX())
|
||||
, mY(cell->getCell()->getGridY())
|
||||
, mSceneManager(sceneManager)
|
||||
, mBulletShapeManager(bulletShapeManager)
|
||||
, mKeyframeManager(keyframeManager)
|
||||
, mTerrain(terrain)
|
||||
{
|
||||
ListModelsVisitor visitor (mMeshes);
|
||||
if (cell->getState() == MWWorld::CellStore::State_Loaded)
|
||||
{
|
||||
cell->forEach(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::vector<std::string>& objectIds = cell->getPreloadedIds();
|
||||
|
||||
// could possibly build the model list in the worker thread if we manage to make the Store thread safe
|
||||
for (std::vector<std::string>::const_iterator it = objectIds.begin(); it != objectIds.end(); ++it)
|
||||
{
|
||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), *it);
|
||||
std::string model = ref.getPtr().getClass().getModel(ref.getPtr());
|
||||
if (!model.empty())
|
||||
mMeshes.push_back(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Preload work to be called from the worker thread.
|
||||
virtual void doWork()
|
||||
{
|
||||
for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string mesh = *it;
|
||||
mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS());
|
||||
|
||||
mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh));
|
||||
mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh));
|
||||
|
||||
size_t slashpos = mesh.find_last_of("/\\");
|
||||
if (slashpos != std::string::npos && slashpos != mesh.size()-1)
|
||||
{
|
||||
Misc::StringUtils::lowerCaseInPlace(mesh);
|
||||
if (mesh[slashpos+1] == 'x')
|
||||
{
|
||||
std::string kfname = mesh;
|
||||
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
|
||||
{
|
||||
kfname.replace(kfname.size()-4, 4, ".kf");
|
||||
mPreloadedObjects.push_back(mKeyframeManager->get(kfname));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
// ignore error for now, would spam the log too much
|
||||
// error will be shown when visiting the cell
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsExterior)
|
||||
{
|
||||
try
|
||||
{
|
||||
mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY));
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::vector<std::string> MeshList;
|
||||
bool mIsExterior;
|
||||
int mX;
|
||||
int mY;
|
||||
MeshList mMeshes;
|
||||
Resource::SceneManager* mSceneManager;
|
||||
Resource::BulletShapeManager* mBulletShapeManager;
|
||||
Resource::KeyframeManager* mKeyframeManager;
|
||||
Terrain::World* mTerrain;
|
||||
|
||||
// keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state
|
||||
std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects;
|
||||
};
|
||||
|
||||
/// Worker thread item: update the resource system's cache, effectively deleting unused entries.
|
||||
class UpdateCacheItem : public SceneUtil::WorkItem
|
||||
{
|
||||
public:
|
||||
UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime)
|
||||
: mReferenceTime(referenceTime)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mTerrain(terrain)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void doWork()
|
||||
{
|
||||
mResourceSystem->updateCache(mReferenceTime);
|
||||
|
||||
mTerrain->updateCache();
|
||||
}
|
||||
|
||||
private:
|
||||
double mReferenceTime;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
Terrain::World* mTerrain;
|
||||
};
|
||||
|
||||
CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain)
|
||||
: mResourceSystem(resourceSystem)
|
||||
, mBulletShapeManager(bulletShapeManager)
|
||||
, mTerrain(terrain)
|
||||
, mExpiryDelay(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
void CellPreloader::preload(CellStore *cell, double timestamp)
|
||||
{
|
||||
if (!mWorkQueue)
|
||||
{
|
||||
std::cerr << "can't preload, no work queue set " << std::endl;
|
||||
return;
|
||||
}
|
||||
if (cell->getState() == CellStore::State_Unloaded)
|
||||
{
|
||||
std::cerr << "can't preload objects for unloaded cell" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
PreloadMap::iterator found = mPreloadCells.find(cell);
|
||||
if (found != mPreloadCells.end())
|
||||
{
|
||||
// already preloaded, nothing to do other than updating the timestamp
|
||||
found->second.mTimeStamp = timestamp;
|
||||
return;
|
||||
}
|
||||
|
||||
osg::ref_ptr<PreloadItem> item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain));
|
||||
mWorkQueue->addWorkItem(item);
|
||||
|
||||
mPreloadCells[cell] = PreloadEntry(timestamp, item);
|
||||
}
|
||||
|
||||
void CellPreloader::notifyLoaded(CellStore *cell)
|
||||
{
|
||||
mPreloadCells.erase(cell);
|
||||
}
|
||||
|
||||
void CellPreloader::updateCache(double timestamp)
|
||||
{
|
||||
// TODO: add settings for a minimum/maximum size of the cache
|
||||
|
||||
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();)
|
||||
{
|
||||
if (it->second.mTimeStamp < timestamp - mExpiryDelay)
|
||||
mPreloadCells.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
// the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations
|
||||
mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp));
|
||||
}
|
||||
|
||||
void CellPreloader::setExpiryDelay(double expiryDelay)
|
||||
{
|
||||
mExpiryDelay = expiryDelay;
|
||||
}
|
||||
|
||||
void CellPreloader::setWorkQueue(osg::ref_ptr<SceneUtil::WorkQueue> workQueue)
|
||||
{
|
||||
mWorkQueue = workQueue;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
#ifndef OPENMW_MWWORLD_CELLPRELOADER_H
|
||||
#define OPENMW_MWWORLD_CELLPRELOADER_H
|
||||
|
||||
#include <map>
|
||||
#include <osg/ref_ptr>
|
||||
#include <components/sceneutil/workqueue.hpp>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
class BulletShapeManager;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class World;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
|
||||
class CellPreloader
|
||||
{
|
||||
public:
|
||||
CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain);
|
||||
|
||||
/// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell.
|
||||
/// @note The cell itself must be in State_Loaded or State_Preloaded.
|
||||
void preload(MWWorld::CellStore* cell, double timestamp);
|
||||
|
||||
void notifyLoaded(MWWorld::CellStore* cell);
|
||||
|
||||
/// Removes preloaded cells that have not had a preload request for a while.
|
||||
void updateCache(double timestamp);
|
||||
|
||||
/// How long to keep a preloaded cell in cache after it's no longer requested.
|
||||
void setExpiryDelay(double expiryDelay);
|
||||
|
||||
void setWorkQueue(osg::ref_ptr<SceneUtil::WorkQueue> workQueue);
|
||||
|
||||
private:
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
Resource::BulletShapeManager* mBulletShapeManager;
|
||||
Terrain::World* mTerrain;
|
||||
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
|
||||
double mExpiryDelay;
|
||||
|
||||
struct PreloadEntry
|
||||
{
|
||||
PreloadEntry(double timestamp, osg::ref_ptr<SceneUtil::WorkItem> workItem)
|
||||
: mTimeStamp(timestamp)
|
||||
, mWorkItem(workItem)
|
||||
{
|
||||
}
|
||||
PreloadEntry()
|
||||
: mTimeStamp(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
double mTimeStamp;
|
||||
osg::ref_ptr<SceneUtil::WorkItem> mWorkItem;
|
||||
};
|
||||
typedef std::map<const MWWorld::CellStore*, PreloadEntry> PreloadMap;
|
||||
|
||||
// Cells that are currently being preloaded, or have already finished preloading
|
||||
PreloadMap mPreloadCells;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,148 @@
|
||||
#include "imagemanager.hpp"
|
||||
|
||||
#include <osgDB/Registry>
|
||||
#include <osg/GLExtensions>
|
||||
#include <osg/Version>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "objectcache.hpp"
|
||||
|
||||
#ifdef OSG_LIBRARY_STATIC
|
||||
// This list of plugins should match with the list in the top-level CMakelists.txt.
|
||||
USE_OSGPLUGIN(png)
|
||||
USE_OSGPLUGIN(tga)
|
||||
USE_OSGPLUGIN(dds)
|
||||
USE_OSGPLUGIN(jpeg)
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
osg::ref_ptr<osg::Image> createWarningImage()
|
||||
{
|
||||
osg::ref_ptr<osg::Image> warningImage = new osg::Image;
|
||||
|
||||
int width = 8, height = 8;
|
||||
warningImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE);
|
||||
assert (warningImage->isDataContiguous());
|
||||
unsigned char* data = warningImage->data();
|
||||
for (int i=0;i<width*height;++i)
|
||||
{
|
||||
data[3*i] = (255);
|
||||
data[3*i+1] = (0);
|
||||
data[3*i+2] = (255);
|
||||
}
|
||||
return warningImage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
ImageManager::ImageManager(const VFS::Manager *vfs)
|
||||
: ResourceManager(vfs)
|
||||
, mWarningImage(createWarningImage())
|
||||
, mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba"))
|
||||
{
|
||||
}
|
||||
|
||||
ImageManager::~ImageManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool checkSupported(osg::Image* image, const std::string& filename)
|
||||
{
|
||||
switch(image->getPixelFormat())
|
||||
{
|
||||
case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT):
|
||||
case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT):
|
||||
case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT):
|
||||
case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT):
|
||||
{
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3)
|
||||
osg::GLExtensions* exts = osg::GLExtensions::Get(0, false);
|
||||
if (exts && !exts->isTextureCompressionS3TCSupported
|
||||
// This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG.
|
||||
&& !osg::isGLExtensionSupported(0, "GL_S3_s3tc"))
|
||||
#else
|
||||
osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false);
|
||||
if (exts && !exts->isTextureCompressionS3TCSupported()
|
||||
// This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG.
|
||||
&& !osg::isGLExtensionSupported(0, "GL_S3_s3tc"))
|
||||
#endif
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> ImageManager::getImage(const std::string &filename)
|
||||
{
|
||||
std::string normalized = filename;
|
||||
mVFS->normalizeFilename(normalized);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(normalized);
|
||||
if (obj)
|
||||
return osg::ref_ptr<osg::Image>(static_cast<osg::Image*>(obj.get()));
|
||||
else
|
||||
{
|
||||
Files::IStreamPtr stream;
|
||||
try
|
||||
{
|
||||
stream = mVFS->get(normalized.c_str());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::cerr << "Failed to open image: " << e.what() << std::endl;
|
||||
mCache->addEntryToObjectCache(normalized, mWarningImage);
|
||||
return mWarningImage;
|
||||
}
|
||||
|
||||
size_t extPos = normalized.find_last_of('.');
|
||||
std::string ext;
|
||||
if (extPos != std::string::npos && extPos+1 < normalized.size())
|
||||
ext = normalized.substr(extPos+1);
|
||||
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext);
|
||||
if (!reader)
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl;
|
||||
mCache->addEntryToObjectCache(normalized, mWarningImage);
|
||||
return mWarningImage;
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions);
|
||||
if (!result.success())
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl;
|
||||
mCache->addEntryToObjectCache(normalized, mWarningImage);
|
||||
return mWarningImage;
|
||||
}
|
||||
|
||||
osg::Image* image = result.getImage();
|
||||
if (!checkSupported(image, filename))
|
||||
{
|
||||
mCache->addEntryToObjectCache(normalized, mWarningImage);
|
||||
return mWarningImage;
|
||||
}
|
||||
|
||||
mCache->addEntryToObjectCache(normalized, image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
osg::Image *ImageManager::getWarningImage()
|
||||
{
|
||||
return mWarningImage;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
#ifndef OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H
|
||||
#define OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Image>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
#include "resourcemanager.hpp"
|
||||
|
||||
namespace osgViewer
|
||||
{
|
||||
class Viewer;
|
||||
}
|
||||
|
||||
namespace osgDB
|
||||
{
|
||||
class Options;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
/// @brief Handles loading/caching of Images.
|
||||
/// @note May be used from any thread.
|
||||
class ImageManager : public ResourceManager
|
||||
{
|
||||
public:
|
||||
ImageManager(const VFS::Manager* vfs);
|
||||
~ImageManager();
|
||||
|
||||
/// Create or retrieve an Image
|
||||
/// Returns the dummy image if the given image is not found.
|
||||
osg::ref_ptr<osg::Image> getImage(const std::string& filename);
|
||||
|
||||
osg::Image* getWarningImage();
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Image> mWarningImage;
|
||||
osg::ref_ptr<osgDB::Options> mOptions;
|
||||
|
||||
ImageManager(const ImageManager&);
|
||||
void operator = (const ImageManager&);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,79 @@
|
||||
#include "multiobjectcache.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
MultiObjectCache::MultiObjectCache()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MultiObjectCache::~MultiObjectCache()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MultiObjectCache::removeUnreferencedObjectsInCache()
|
||||
{
|
||||
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
|
||||
|
||||
// Remove unreferenced entries from object cache
|
||||
ObjectCacheMap::iterator oitr = _objectCache.begin();
|
||||
while(oitr != _objectCache.end())
|
||||
{
|
||||
if (oitr->second->referenceCount() <= 1)
|
||||
{
|
||||
objectsToRemove.push_back(oitr->second);
|
||||
_objectCache.erase(oitr++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++oitr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note, actual unref happens outside of the lock
|
||||
objectsToRemove.clear();
|
||||
}
|
||||
|
||||
void MultiObjectCache::addEntryToObjectCache(const std::string &filename, osg::Object *object)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
|
||||
_objectCache.insert(std::make_pair(filename, object));
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Object> MultiObjectCache::takeFromObjectCache(const std::string &fileName)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
|
||||
ObjectCacheMap::iterator found = _objectCache.find(fileName);
|
||||
if (found == _objectCache.end())
|
||||
return osg::ref_ptr<osg::Object>();
|
||||
else
|
||||
{
|
||||
osg::ref_ptr<osg::Object> object = found->second;
|
||||
_objectCache.erase(found);
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiObjectCache::releaseGLObjects(osg::State *state)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
|
||||
|
||||
for(ObjectCacheMap::iterator itr = _objectCache.begin();
|
||||
itr != _objectCache.end();
|
||||
++itr)
|
||||
{
|
||||
osg::Object* object = itr->second.get();
|
||||
object->releaseGLObjects(state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
#ifndef OPENMW_COMPONENTS_MULTIOBJECTCACHE_H
|
||||
#define OPENMW_COMPONENTS_MULTIOBJECTCACHE_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Referenced>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Object;
|
||||
class State;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
/// @brief Cache for "non reusable" objects.
|
||||
class MultiObjectCache : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
MultiObjectCache();
|
||||
~MultiObjectCache();
|
||||
|
||||
void removeUnreferencedObjectsInCache();
|
||||
|
||||
void addEntryToObjectCache(const std::string& filename, osg::Object* object);
|
||||
|
||||
/** Take an Object from cache. Return NULL if no object found. */
|
||||
osg::ref_ptr<osg::Object> takeFromObjectCache(const std::string& fileName);
|
||||
|
||||
/** call releaseGLObjects on all objects attached to the object cache.*/
|
||||
void releaseGLObjects(osg::State* state);
|
||||
|
||||
protected:
|
||||
|
||||
typedef std::multimap<std::string, osg::ref_ptr<osg::Object> > ObjectCacheMap;
|
||||
|
||||
ObjectCacheMap _objectCache;
|
||||
OpenThreads::Mutex _objectCacheMutex;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,32 @@
|
||||
#include "resourcemanager.hpp"
|
||||
|
||||
#include "objectcache.hpp"
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
ResourceManager::ResourceManager(const VFS::Manager *vfs)
|
||||
: mVFS(vfs)
|
||||
, mCache(new Resource::ObjectCache)
|
||||
, mExpiryDelay(0.0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ResourceManager::updateCache(double referenceTime)
|
||||
{
|
||||
mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime);
|
||||
mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay);
|
||||
}
|
||||
|
||||
void ResourceManager::setExpiryDelay(double expiryDelay)
|
||||
{
|
||||
mExpiryDelay = expiryDelay;
|
||||
}
|
||||
|
||||
const VFS::Manager* ResourceManager::getVFS() const
|
||||
{
|
||||
return mVFS;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
#ifndef OPENMW_COMPONENTS_RESOURCE_MANAGER_H
|
||||
#define OPENMW_COMPONENTS_RESOURCE_MANAGER_H
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ObjectCache;
|
||||
|
||||
/// @brief Base class for managers that require a virtual file system and object cache.
|
||||
/// @par This base class implements clearing of the cache, but populating it and what it's used for is up to the individual sub classes.
|
||||
class ResourceManager
|
||||
{
|
||||
public:
|
||||
ResourceManager(const VFS::Manager* vfs);
|
||||
|
||||
/// Clear cache entries that have not been referenced for longer than expiryDelay.
|
||||
virtual void updateCache(double referenceTime);
|
||||
|
||||
/// How long to keep objects in cache after no longer being referenced.
|
||||
void setExpiryDelay (double expiryDelay);
|
||||
|
||||
const VFS::Manager* getVFS() const;
|
||||
|
||||
protected:
|
||||
const VFS::Manager* mVFS;
|
||||
osg::ref_ptr<Resource::ObjectCache> mCache;
|
||||
double mExpiryDelay;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,314 +0,0 @@
|
||||
#include "texturemanager.hpp"
|
||||
|
||||
#include <osgDB/Registry>
|
||||
#include <osg/GLExtensions>
|
||||
#include <osg/Version>
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#ifdef OSG_LIBRARY_STATIC
|
||||
// This list of plugins should match with the list in the top-level CMakelists.txt.
|
||||
USE_OSGPLUGIN(png)
|
||||
USE_OSGPLUGIN(tga)
|
||||
USE_OSGPLUGIN(dds)
|
||||
USE_OSGPLUGIN(jpeg)
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> createWarningTexture()
|
||||
{
|
||||
osg::ref_ptr<osg::Image> warningImage = new osg::Image;
|
||||
|
||||
int width = 8, height = 8;
|
||||
warningImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE);
|
||||
assert (warningImage->isDataContiguous());
|
||||
unsigned char* data = warningImage->data();
|
||||
for (int i=0;i<width*height;++i)
|
||||
{
|
||||
data[3*i] = (255);
|
||||
data[3*i+1] = (0);
|
||||
data[3*i+2] = (255);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> warningTexture = new osg::Texture2D;
|
||||
warningTexture->setImage(warningImage);
|
||||
return warningTexture;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
TextureManager::TextureManager(const VFS::Manager *vfs)
|
||||
: mVFS(vfs)
|
||||
, mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR)
|
||||
, mMagFilter(osg::Texture::LINEAR)
|
||||
, mMaxAnisotropy(1)
|
||||
, mWarningTexture(createWarningTexture())
|
||||
, mUnRefImageDataAfterApply(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TextureManager::~TextureManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TextureManager::setUnRefImageDataAfterApply(bool unref)
|
||||
{
|
||||
mUnRefImageDataAfterApply = unref;
|
||||
}
|
||||
|
||||
void TextureManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter,
|
||||
const std::string &mipmap, int maxAnisotropy,
|
||||
osgViewer::Viewer *viewer)
|
||||
{
|
||||
osg::Texture::FilterMode min = osg::Texture::LINEAR;
|
||||
osg::Texture::FilterMode mag = osg::Texture::LINEAR;
|
||||
|
||||
if(magfilter == "nearest")
|
||||
mag = osg::Texture::NEAREST;
|
||||
else if(magfilter != "linear")
|
||||
std::cerr<< "Invalid texture mag filter: "<<magfilter <<std::endl;
|
||||
|
||||
if(minfilter == "nearest")
|
||||
min = osg::Texture::NEAREST;
|
||||
else if(minfilter != "linear")
|
||||
std::cerr<< "Invalid texture min filter: "<<minfilter <<std::endl;
|
||||
|
||||
if(mipmap == "nearest")
|
||||
{
|
||||
if(min == osg::Texture::NEAREST)
|
||||
min = osg::Texture::NEAREST_MIPMAP_NEAREST;
|
||||
else if(min == osg::Texture::LINEAR)
|
||||
min = osg::Texture::LINEAR_MIPMAP_NEAREST;
|
||||
}
|
||||
else if(mipmap != "none")
|
||||
{
|
||||
if(mipmap != "linear")
|
||||
std::cerr<< "Invalid texture mipmap: "<<mipmap <<std::endl;
|
||||
if(min == osg::Texture::NEAREST)
|
||||
min = osg::Texture::NEAREST_MIPMAP_LINEAR;
|
||||
else if(min == osg::Texture::LINEAR)
|
||||
min = osg::Texture::LINEAR_MIPMAP_LINEAR;
|
||||
}
|
||||
|
||||
if(viewer) viewer->stopThreading();
|
||||
setFilterSettings(min, mag, maxAnisotropy);
|
||||
if(viewer) viewer->startThreading();
|
||||
}
|
||||
|
||||
void TextureManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy)
|
||||
{
|
||||
mMinFilter = minFilter;
|
||||
mMagFilter = magFilter;
|
||||
mMaxAnisotropy = std::max(1, maxAnisotropy);
|
||||
|
||||
for (std::map<MapKey, osg::ref_ptr<osg::Texture2D> >::iterator it = mTextures.begin(); it != mTextures.end(); ++it)
|
||||
{
|
||||
osg::ref_ptr<osg::Texture2D> tex = it->second;
|
||||
|
||||
// Keep mip-mapping disabled if the texture creator explicitely requested no mipmapping.
|
||||
osg::Texture::FilterMode oldMin = tex->getFilter(osg::Texture::MIN_FILTER);
|
||||
if (oldMin == osg::Texture::LINEAR || oldMin == osg::Texture::NEAREST)
|
||||
{
|
||||
osg::Texture::FilterMode newMin = osg::Texture::LINEAR;
|
||||
switch (mMinFilter)
|
||||
{
|
||||
case osg::Texture::LINEAR:
|
||||
case osg::Texture::LINEAR_MIPMAP_LINEAR:
|
||||
case osg::Texture::LINEAR_MIPMAP_NEAREST:
|
||||
newMin = osg::Texture::LINEAR;
|
||||
break;
|
||||
case osg::Texture::NEAREST:
|
||||
case osg::Texture::NEAREST_MIPMAP_LINEAR:
|
||||
case osg::Texture::NEAREST_MIPMAP_NEAREST:
|
||||
newMin = osg::Texture::NEAREST;
|
||||
break;
|
||||
}
|
||||
tex->setFilter(osg::Texture::MIN_FILTER, newMin);
|
||||
}
|
||||
else
|
||||
tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter);
|
||||
|
||||
tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter);
|
||||
tex->setMaxAnisotropy(static_cast<float>(mMaxAnisotropy));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
osg::ref_ptr<osg::Image> TextureManager::getImage(const std::string &filename)
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
bool checkSupported(osg::Image* image, const std::string& filename)
|
||||
{
|
||||
switch(image->getPixelFormat())
|
||||
{
|
||||
case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT):
|
||||
case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT):
|
||||
case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT):
|
||||
case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT):
|
||||
{
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3)
|
||||
osg::GLExtensions* exts = osg::GLExtensions::Get(0, false);
|
||||
if (exts && !exts->isTextureCompressionS3TCSupported
|
||||
// This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG.
|
||||
&& !osg::isGLExtensionSupported(0, "GL_S3_s3tc"))
|
||||
#else
|
||||
osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false);
|
||||
if (exts && !exts->isTextureCompressionS3TCSupported()
|
||||
// This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG.
|
||||
&& !osg::isGLExtensionSupported(0, "GL_S3_s3tc"))
|
||||
#endif
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> TextureManager::getImage(const std::string &filename)
|
||||
{
|
||||
std::string normalized = filename;
|
||||
mVFS->normalizeFilename(normalized);
|
||||
std::map<std::string, osg::ref_ptr<osg::Image> >::iterator found = mImages.find(normalized);
|
||||
if (found != mImages.end())
|
||||
return found->second;
|
||||
else
|
||||
{
|
||||
Files::IStreamPtr stream;
|
||||
try
|
||||
{
|
||||
stream = mVFS->get(normalized.c_str());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::cerr << "Failed to open image: " << e.what() << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osgDB::Options> opts (new osgDB::Options);
|
||||
opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option
|
||||
size_t extPos = normalized.find_last_of('.');
|
||||
std::string ext;
|
||||
if (extPos != std::string::npos && extPos+1 < normalized.size())
|
||||
ext = normalized.substr(extPos+1);
|
||||
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext);
|
||||
if (!reader)
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts);
|
||||
if (!result.success())
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
osg::Image* image = result.getImage();
|
||||
if (!checkSupported(image, filename))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mImages.insert(std::make_pair(normalized, image));
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> TextureManager::getTexture2D(const std::string &filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT)
|
||||
{
|
||||
std::string normalized = filename;
|
||||
mVFS->normalizeFilename(normalized);
|
||||
MapKey key = std::make_pair(std::make_pair(wrapS, wrapT), normalized);
|
||||
std::map<MapKey, osg::ref_ptr<osg::Texture2D> >::iterator found = mTextures.find(key);
|
||||
if (found != mTextures.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
Files::IStreamPtr stream;
|
||||
try
|
||||
{
|
||||
stream = mVFS->get(normalized.c_str());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::cerr << "Failed to open texture: " << e.what() << std::endl;
|
||||
return mWarningTexture;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osgDB::Options> opts (new osgDB::Options);
|
||||
opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option
|
||||
size_t extPos = normalized.find_last_of('.');
|
||||
std::string ext;
|
||||
if (extPos != std::string::npos && extPos+1 < normalized.size())
|
||||
ext = normalized.substr(extPos+1);
|
||||
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext);
|
||||
if (!reader)
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl;
|
||||
return mWarningTexture;
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts);
|
||||
if (!result.success())
|
||||
{
|
||||
std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl;
|
||||
return mWarningTexture;
|
||||
}
|
||||
|
||||
osg::Image* image = result.getImage();
|
||||
if (!checkSupported(image, filename))
|
||||
{
|
||||
return mWarningTexture;
|
||||
}
|
||||
|
||||
// We need to flip images, because the Morrowind texture coordinates use the DirectX convention (top-left image origin),
|
||||
// but OpenGL uses bottom left as the image origin.
|
||||
// For some reason this doesn't concern DDS textures, which are already flipped when loaded.
|
||||
if (ext != "dds")
|
||||
{
|
||||
image->flipVertical();
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> texture(new osg::Texture2D);
|
||||
texture->setImage(image);
|
||||
texture->setWrap(osg::Texture::WRAP_S, wrapS);
|
||||
texture->setWrap(osg::Texture::WRAP_T, wrapT);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, mMinFilter);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, mMagFilter);
|
||||
texture->setMaxAnisotropy(mMaxAnisotropy);
|
||||
|
||||
texture->setUnRefImageDataAfterApply(mUnRefImageDataAfterApply);
|
||||
|
||||
mTextures.insert(std::make_pair(key, texture));
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
osg::Texture2D* TextureManager::getWarningTexture()
|
||||
{
|
||||
return mWarningTexture.get();
|
||||
}
|
||||
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
#ifndef OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H
|
||||
#define OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Image>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
namespace osgViewer
|
||||
{
|
||||
class Viewer;
|
||||
}
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
/// @brief Handles loading/caching of Images and Texture StateAttributes.
|
||||
class TextureManager
|
||||
{
|
||||
public:
|
||||
TextureManager(const VFS::Manager* vfs);
|
||||
~TextureManager();
|
||||
|
||||
void setFilterSettings(const std::string &magfilter, const std::string &minfilter,
|
||||
const std::string &mipmap, int maxAnisotropy,
|
||||
osgViewer::Viewer *view);
|
||||
|
||||
/// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts,
|
||||
/// otherwise should be disabled to reduce memory usage.
|
||||
void setUnRefImageDataAfterApply(bool unref);
|
||||
|
||||
/// Create or retrieve a Texture2D using the specified image filename, and wrap parameters.
|
||||
/// Returns the dummy texture if the given texture is not found.
|
||||
osg::ref_ptr<osg::Texture2D> getTexture2D(const std::string& filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT);
|
||||
|
||||
/// Create or retrieve an Image
|
||||
osg::ref_ptr<osg::Image> getImage(const std::string& filename);
|
||||
|
||||
const VFS::Manager* getVFS() { return mVFS; }
|
||||
|
||||
osg::Texture2D* getWarningTexture();
|
||||
|
||||
private:
|
||||
const VFS::Manager* mVFS;
|
||||
|
||||
osg::Texture::FilterMode mMinFilter;
|
||||
osg::Texture::FilterMode mMagFilter;
|
||||
int mMaxAnisotropy;
|
||||
|
||||
typedef std::pair<std::pair<int, int>, std::string> MapKey;
|
||||
|
||||
std::map<std::string, osg::ref_ptr<osg::Image> > mImages;
|
||||
|
||||
std::map<MapKey, osg::ref_ptr<osg::Texture2D> > mTextures;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> mWarningTexture;
|
||||
|
||||
bool mUnRefImageDataAfterApply;
|
||||
|
||||
/// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first!
|
||||
void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy);
|
||||
|
||||
TextureManager(const TextureManager&);
|
||||
void operator = (const TextureManager&);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,48 @@
|
||||
#include "unrefqueue.hpp"
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include <osg/Object>
|
||||
//#include <osg/Timer>
|
||||
//#include <iostream>
|
||||
|
||||
#include <components/sceneutil/workqueue.hpp>
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
|
||||
class UnrefWorkItem : public SceneUtil::WorkItem
|
||||
{
|
||||
public:
|
||||
std::deque<osg::ref_ptr<const osg::Object> > mObjects;
|
||||
|
||||
virtual void doWork()
|
||||
{
|
||||
//osg::Timer timer;
|
||||
//size_t objcount = mObjects.size();
|
||||
mObjects.clear();
|
||||
//std::cout << "cleared " << objcount << " objects in " << timer.time_m() << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
UnrefQueue::UnrefQueue()
|
||||
{
|
||||
mWorkItem = new UnrefWorkItem;
|
||||
}
|
||||
|
||||
void UnrefQueue::push(const osg::Object *obj)
|
||||
{
|
||||
mWorkItem->mObjects.push_back(obj);
|
||||
}
|
||||
|
||||
void UnrefQueue::flush(SceneUtil::WorkQueue *workQueue)
|
||||
{
|
||||
if (mWorkItem->mObjects.empty())
|
||||
return;
|
||||
|
||||
workQueue->addWorkItem(mWorkItem);
|
||||
|
||||
mWorkItem = new UnrefWorkItem;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
#ifndef OPENMW_COMPONENTS_UNREFQUEUE_H
|
||||
#define OPENMW_COMPONENTS_UNREFQUEUE_H
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Referenced>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Object;
|
||||
}
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
class WorkQueue;
|
||||
class UnrefWorkItem;
|
||||
|
||||
/// @brief Handles unreferencing of objects through the WorkQueue. Typical use scenario
|
||||
/// would be the main thread pushing objects that are no longer needed, and the background thread deleting them.
|
||||
class UnrefQueue : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
UnrefQueue();
|
||||
|
||||
/// Adds an object to the list of objects to be unreferenced. Call from the main thread.
|
||||
void push(const osg::Object* obj);
|
||||
|
||||
/// Adds a WorkItem to the given WorkQueue that will clear the list of objects in a worker thread, thus unreferencing them.
|
||||
/// Call from the main thread.
|
||||
void flush(SceneUtil::WorkQueue* workQueue);
|
||||
|
||||
private:
|
||||
osg::ref_ptr<UnrefWorkItem> mWorkItem;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue