This commit is contained in:
gallaert
2018-12-06 12:35:21 +00:00
18 changed files with 3369 additions and 178 deletions

View File

@@ -6,10 +6,10 @@ using namespace osg;
/**
* merge OSG output into our logging system, so it gets recorded to file,
* and so we can display a GUI console with renderer issues, especially
* shader compilation warnings and errors.
*/
* merge OSG output into our logging system, so it gets recorded to file,
* and so we can display a GUI console with renderer issues, especially
* shader compilation warnings and errors.
*/
class NotifyLogger : public osg::NotifyHandler
{
public:
@@ -22,24 +22,33 @@ public:
if (strstr(message, "the final reference count was")) {
// as this is going to segfault ignore the translation of severity and always output the message.
SG_LOG(SG_GL, SG_ALERT, message);
int* trigger_segfault = 0;
*trigger_segfault = 0;
#ifndef DEBUG
throw new std::string(message);
//int* trigger_segfault = 0;
//*trigger_segfault = 0;
#endif
return;
}
SG_LOG(SG_GL, translateSeverity(severity), message);
char*tmessage = strdup(message);
char*lf = strrchr(tmessage, '\n');
if (lf)
*lf = 0;
SG_LOG(SG_OSG, translateSeverity(severity), tmessage);
free(tmessage);
}
private:
sgDebugPriority translateSeverity(osg::NotifySeverity severity) {
switch (severity) {
case osg::ALWAYS:
case osg::FATAL: return SG_ALERT;
case osg::WARN: return SG_WARN;
case osg::NOTICE:
case osg::INFO: return SG_INFO;
case osg::DEBUG_FP:
case osg::DEBUG_INFO: return SG_DEBUG;
default: return SG_ALERT;
case osg::ALWAYS:
case osg::FATAL: return SG_ALERT;
case osg::WARN: return SG_WARN;
case osg::NOTICE:
case osg::INFO: return SG_INFO;
case osg::DEBUG_FP:
case osg::DEBUG_INFO: return SG_DEBUG;
default: return SG_ALERT;
}
}
};

View File

@@ -35,7 +35,10 @@ typedef enum {
SG_TERRASYNC = 0x01000000,
SG_PARTICLES = 0x02000000,
SG_HEADLESS = 0x04000000,
SG_UNDEFD = 0x08000000, // For range checking
// SG_OSG (OSG notify) - will always be displayed regardless of FG log settings as OSG log level is configured
// separately and thus it makes more sense to allow these message through.
SG_OSG = 0x08000000,
SG_UNDEFD = 0x10000000, // For range checking
SG_ALL = 0xFFFFFFFF
} sgDebugClass;

View File

@@ -38,6 +38,7 @@
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/timing/timestamp.hxx>
#if defined (SG_WINDOWS)
// for AllocConsole, OutputDebugString
@@ -61,7 +62,12 @@ LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
{
return ((c & m_class) != 0 && p >= m_priority);
if ((c & m_class) != 0 && p >= m_priority)
return true;
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
return true;
return false;
}
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
@@ -69,6 +75,18 @@ void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
m_priority = p;
m_class = c;
}
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
{
switch (p) {
case SG_ALERT: return "ALRT";
case SG_BULK: return "BULK";
case SG_DEBUG: return "DBUG";
case SG_INFO: return "INFO";
case SG_POPUP: return "POPU";
case SG_WARN: return "WARN";
default: return "UNKN";
}
}
const char* LogCallback::debugClassToString(sgDebugClass c)
{
@@ -101,6 +119,7 @@ const char* LogCallback::debugClassToString(sgDebugClass c)
case SG_TERRASYNC: return "terrasync";
case SG_PARTICLES: return "particles";
case SG_HEADLESS: return "headless";
case SG_OSG: return "OSG";
default: return "unknown";
}
}
@@ -112,18 +131,41 @@ const char* LogCallback::debugClassToString(sgDebugClass c)
class FileLogCallback : public simgear::LogCallback
{
public:
SGTimeStamp logTimer;
FileLogCallback(const SGPath& aPath, sgDebugClass c, sgDebugPriority p) :
simgear::LogCallback(c, p)
{
m_file.open(aPath, std::ios_base::out | std::ios_base::trunc);
logTimer.stamp();
}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& message)
{
if (!shouldLog(c, p)) return;
m_file << debugClassToString(c) << ":" << (int) p
<< ":" << file << ":" << line << ":" << message << std::endl;
// fprintf(stderr, "%7.2f [%.8s]:%-10s %s\n", logTimer.elapsedMSec() / 1000.0, debugPriorityToString(p), debugClassToString(c), aMessage.c_str());
m_file
<< std::fixed
<< std::setprecision(2)
<< std::setw(8)
<< std::right
<< (logTimer.elapsedMSec() / 1000.0)
<< std::setw(8)
<< std::left
<< " ["+std::string(debugPriorityToString(p))+"]:"
<< std::setw(10)
<< std::left
<< debugClassToString(c)
<< " "
<< file
<< ":"
<< line
<< ":"
<< message << std::endl;
//m_file << debugClassToString(c) << ":" << (int)p
// << ":" << file << ":" << line << ":" << message << std::endl;
}
private:
sg_ofstream m_file;
@@ -132,9 +174,12 @@ private:
class StderrLogCallback : public simgear::LogCallback
{
public:
SGTimeStamp logTimer;
StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
simgear::LogCallback(c, p)
{
logTimer.stamp();
}
#if defined (SG_WINDOWS)
@@ -148,8 +193,9 @@ public:
const char* file, int line, const std::string& aMessage)
{
if (!shouldLog(c, p)) return;
fprintf(stderr, "%s\n", aMessage.c_str());
//fprintf(stderr, "%s\n", aMessage.c_str());
fprintf(stderr, "%8.2f [%.8s]:%-10s %s\n", logTimer.elapsedMSec()/1000.0, debugPriorityToString(p), debugClassToString(c), aMessage.c_str());
// file, line, aMessage.c_str());
//fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
// file, line, aMessage.c_str());
fflush(stderr);
@@ -470,6 +516,10 @@ public:
// Testing mode, so always log.
if (m_testMode) return true;
// SG_OSG (OSG notify) - will always be displayed regardless of FG log settings as OSG log level is configured
// separately and thus it makes more sense to allow these message through.
if (p == SG_OSG) return true;
p = translatePriority(p);
if (p >= SG_INFO) return true;
return ((c & m_logClass) != 0 && p >= m_logPriority);

View File

@@ -52,6 +52,7 @@ protected:
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
static const char* debugClassToString(sgDebugClass c);
static const char* debugPriorityToString(sgDebugPriority p);
private:
sgDebugClass m_class;
sgDebugPriority m_priority;

View File

@@ -92,6 +92,12 @@ public:
return p;
}
static PropertyObject<T> create(SGPropertyNode_ptr aNode)
{
PropertyObject<T> p(aNode);
return p;
}
static PropertyObject<T> create(SGPropertyNode* aNode, T aValue)
{
PropertyObject<T> p(aNode);
@@ -157,6 +163,8 @@ template <>
class PropertyObject<std::string> : PropertyObjectBase
{
public:
PropertyObject() = default;
explicit PropertyObject(const char* aChild) :
PropertyObjectBase(aChild)
{ }
@@ -183,6 +191,12 @@ public:
return p;
}
static PropertyObject<std::string> create(SGPropertyNode_ptr aNode)
{
PropertyObject<std::string> p(aNode);
return p;
}
static PropertyObject<std::string> create(SGPropertyNode* aNode, const std::string& aValue)
{
PropertyObject<std::string> p(aNode);

View File

@@ -205,6 +205,34 @@ void testCreate()
}
void testDeclare()
{
PropertyObject<bool> a;
PropertyObject<int> b;
PropertyObject<double> c;
PropertyObject<std::string> d;
}
void testDeclareThenDefine()
{
// Declare.
PropertyObject<bool> a;
PropertyObject<std::string> b;
// Property nodes.
SGPropertyNode_ptr propsA = new SGPropertyNode;
SGPropertyNode_ptr propsB = new SGPropertyNode;
propsA->setValue(true);
propsB->setValue("test");
// Now define.
a = PropertyObject<bool>::create(propsA);
b = PropertyObject<std::string>::create(propsB);
assert(a == true);
assert(b == "test");
}
int main(int argc, char* argv[])
{
testRoot = new SGPropertyNode();
@@ -227,6 +255,8 @@ int main(int argc, char* argv[])
testAssignment();
testSTLContainer();
testCreate();
testDeclare();
testDeclareThenDefine();
return EXIT_SUCCESS;
}

View File

@@ -392,6 +392,8 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
image->getInternalTextureFormat(), image->getPixelFormat(),
image->getDataType(), data, osg::Image::USE_NEW_DELETE, image->getPacking() );
mipmaps->setMipmapLevels( mipmapOffsets );
mipmaps->setName(image->getName());
mipmaps->setFileName(image->getFileName());
return mipmaps.release();
}

View File

@@ -22,6 +22,8 @@
#endif
#include "ModelRegistry.hxx"
#include <simgear/scene/util/SGImageUtils.hxx>
#include "../material/mipmap.hxx"
#include <algorithm>
#include <utility>
@@ -44,6 +46,7 @@
#include <osgDB/Registry>
#include <osgDB/SharedStateManager>
#include <osgUtil/Optimizer>
#include <osg/Texture>
#include <simgear/sg_inlines.h>
@@ -61,6 +64,10 @@
#include "BoundingVolumeBuildVisitor.hxx"
#include "model.hxx"
#include <boost/lexical_cast.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
using namespace std;
using namespace osg;
using namespace osgUtil;
@@ -173,110 +180,270 @@ public:
} // namespace
Node* DefaultProcessPolicy::process(Node* node, const string& filename,
const Options* opt)
static bool isPowerOfTwo(int width, int height)
{
return (((width & (width - 1)) == 0) && ((height & (height - 1))) == 0);
}
osg::Node* DefaultProcessPolicy::process(osg::Node* node, const std::string& filename,
const Options* opt)
{
TextureNameVisitor nameVisitor;
node->accept(nameVisitor);
return node;
}
//#define LOCAL_IMAGE_CACHE
#ifdef LOCAL_IMAGE_CACHE
typedef std::map<std::string, osg::ref_ptr<Image>> ImageMap;
ImageMap _imageMap;
//typedef std::map<std::string, osg::ref_ptr<osg::Image> > ImageMap;
//ImageMap _imageMap;
osg::Image* getImageByName(const std::string& filename)
{
ImageMap::iterator itr = _imageMap.find(filename);
if (itr != _imageMap.end()) return itr->second.get();
return nullptr;
}
#endif
ReaderWriter::ReadResult
ModelRegistry::readImage(const string& fileName,
const Options* opt)
const Options* opt)
{
CallbackMap::iterator iter
= imageCallbackMap.find(getFileExtension(fileName));
{
if (iter != imageCallbackMap.end() && iter->second.valid())
return iter->second->readImage(fileName, opt);
string absFileName = SGModelLib::findDataFile(fileName, opt);
if (!fileExists(absFileName)) {
SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
<< fileName << "\"");
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
/*
* processor is the interface to the osg_nvtt plugin
*/
static bool init = false;
static osgDB::ImageProcessor *processor = 0;
int max_texture_size = SGSceneFeatures::instance()->getMaxTextureSize();
if (!init) {
processor = osgDB::Registry::instance()->getImageProcessor();
init = true;
}
bool cache_active = SGSceneFeatures::instance()->getTextureCacheActive();
bool compress_solid = SGSceneFeatures::instance()->getTextureCacheCompressionActive();
bool compress_transparent = SGSceneFeatures::instance()->getTextureCacheCompressionActiveTransparent();
//
// heuristically less than 2048 is more likely to be a badly reported size rather than
// something that is valid so we'll have a minimum size of 2048.
if (max_texture_size < 2048)
max_texture_size = 2048;
std::string fileExtension = getFileExtension(fileName);
CallbackMap::iterator iter = imageCallbackMap.find(fileExtension);
if (iter != imageCallbackMap.end() && iter->second.valid())
return iter->second->readImage(fileName, opt);
string absFileName = SGModelLib::findDataFile(fileName, opt);
if (!fileExists(absFileName)) {
SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
<< fileName << "\"");
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
}
Registry* registry = Registry::instance();
ReaderWriter::ReadResult res;
if (cache_active) {
if (fileExtension != "dds" && fileExtension != "gz") {
std::string root = getPathRoot(absFileName);
std::string prr = getPathRelative(root, absFileName);
std::string cache_root = SGSceneFeatures::instance()->getTextureCompressionPath().c_str();
std::string newName = cache_root + "/" + prr;
SGPath file(absFileName);
std::stringstream tstream;
tstream << std::hex << file.modTime();
newName += "." + tstream.str();
newName += ".cache.dds";
if (!fileExists(newName)) {
res = registry->readImageImplementation(absFileName, opt);
osg::ref_ptr<osg::Image> srcImage = res.getImage();
int width = srcImage->s();
bool transparent = srcImage->isImageTranslucent();
int height = srcImage->t();
if (height >= max_texture_size)
{
SG_LOG(SG_IO, SG_WARN, "Image texture too high " << width << "," << height << absFileName);
osg::ref_ptr<osg::Image> resizedImage;
int factor = height / max_texture_size;
if (ImageUtils::resizeImage(srcImage, width / factor, height / factor, resizedImage))
srcImage = resizedImage;
width = srcImage->s();
height = srcImage->t();
}
if (width >= max_texture_size)
{
SG_LOG(SG_IO, SG_WARN, "Image texture too wide " << width << "," << height << absFileName);
osg::ref_ptr<osg::Image> resizedImage;
int factor = width / max_texture_size;
if (ImageUtils::resizeImage(srcImage, width / factor, height / factor, resizedImage))
srcImage = resizedImage;
width = srcImage->s();
height = srcImage->t();
}
//
// only cache power of two textures that are of a reasonable size
if (width >= 64 && height >= 64 && isPowerOfTwo(width, height)) {
simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE);
SGPath filePath(newName);
filePath.create_dir();
// setup the options string for saving the texture as we don't want OSG to auto flip the texture
// as this complicates loading as it requires a flag to flip it back which will preclude the
// image from being cached because we will have to clone the options to set the flag and thus lose
// the link to the cache in the options from the caller.
osg::ref_ptr<Options> nopt;
nopt = opt->cloneOptions();
std::string optionstring = nopt->getOptionString();
if (!optionstring.empty())
optionstring += " ";
nopt->setOptionString(optionstring + "ddsNoAutoFlipWrite");
/*
* decide if we need to compress this.
*/
bool compress = (transparent && compress_transparent) || (!transparent && compress_solid);
if (compress) {
if (processor)
{
if (transparent)
processor->compress(*srcImage, osg::Texture::USE_S3TC_DXT5_COMPRESSION, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION);
else
processor->compress(*srcImage, osg::Texture::USE_S3TC_DXT1_COMPRESSION, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION);
//processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU);
}
else {
simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE);
SG_LOG(SG_IO, SG_WARN, "Texture compression plugin (osg_nvtt) not available; storing uncompressed image: " << newName);
srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions);
}
}
else {
if (processor) {
processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU);
}
else {
simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE);
srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions);
}
}
registry->writeImage(*srcImage, newName, nopt);
absFileName = newName;
}
}
else
absFileName = newName;
}
}
res = registry->readImageImplementation(absFileName, opt);
Registry* registry = Registry::instance();
ReaderWriter::ReadResult res;
res = registry->readImageImplementation(absFileName, opt);
if (!res.success()) {
SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
return res;
}
if (!res.success()) {
SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
return res;
}
if (res.loadedFromCache())
SG_LOG(SG_IO, SG_BULK, "Returning cached image \""
<< res.getImage()->getFileName() << "\"");
else
SG_LOG(SG_IO, SG_BULK, "Reading image \""
<< res.getImage()->getFileName() << "\"");
osg::ref_ptr<osg::Image> srcImage1 = res.getImage();
//printf(" --> finished loading %s [%s] (%s) %d\n", absFileName.c_str(), srcImage1->getFileName().c_str(), res.loadedFromCache() ? "from cache" : "from disk", res.getImage()->getOrigin());
/*
* Fixup the filename - as when loading from eg. dds.gz the originating filename is lost in the conversion due to the way the OSG loader works
*/
if (srcImage1->getFileName().empty()) {
srcImage1->setFileName(absFileName);
}
if (srcImage1->getName().empty()) {
srcImage1->setName(absFileName);
}
if (res.loadedFromCache())
SG_LOG(SG_IO, SG_BULK, "Returning cached image \""
<< res.getImage()->getFileName() << "\"");
else
SG_LOG(SG_IO, SG_BULK, "Reading image \""
<< res.getImage()->getFileName() << "\"");
// as of March 2018 all patents have expired, https://en.wikipedia.org/wiki/S3_Texture_Compression#Patent
// there is support for S3TC DXT1..5 in MESA https://www.phoronix.com/scan.php?page=news_item&px=S3TC-Lands-In-Mesa
// so it seems that there isn't a valid reason to warn any longer; and beside this is one of those cases where it should
// really only be a developer message
#ifdef WARN_DDS_TEXTURES
// Check for precompressed textures that depend on an extension
switch (res.getImage()->getPixelFormat()) {
switch (res.getImage()->getPixelFormat()) {
// GL_EXT_texture_compression_s3tc
// patented, no way to decompress these
// GL_EXT_texture_compression_s3tc
// patented, no way to decompress these
#ifndef GL_EXT_texture_compression_s3tc
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#endif
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:
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:
// GL_EXT_texture_sRGB
// patented, no way to decompress these
// GL_EXT_texture_sRGB
// patented, no way to decompress these
#ifndef GL_EXT_texture_sRGB
#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C
#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D
#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E
#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F
#endif
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
// GL_TDFX_texture_compression_FXT1
// can decompress these in software but
// no code present in simgear.
// GL_TDFX_texture_compression_FXT1
// can decompress these in software but
// no code present in simgear.
#ifndef GL_3DFX_texture_compression_FXT1
#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0
#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1
#endif
case GL_COMPRESSED_RGB_FXT1_3DFX:
case GL_COMPRESSED_RGBA_FXT1_3DFX:
case GL_COMPRESSED_RGB_FXT1_3DFX:
case GL_COMPRESSED_RGBA_FXT1_3DFX:
// GL_EXT_texture_compression_rgtc
// can decompress these in software but
// no code present in simgear.
// GL_EXT_texture_compression_rgtc
// can decompress these in software but
// no code present in simgear.
#ifndef GL_EXT_texture_compression_rgtc
#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB
#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC
#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD
#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE
#endif
case GL_COMPRESSED_RED_RGTC1_EXT:
case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
case GL_COMPRESSED_RED_RGTC1_EXT:
case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n"
"uses compressed textures which cannot be supported on "
"some systems.\n"
"Please decompress this texture for improved portability.");
break;
SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n"
"uses compressed textures which cannot be supported on "
"some systems.\n"
"Please decompress this texture for improved portability.");
break;
default:
break;
}
return res;
default:
break;
}
#endif
return res;
}
@@ -430,7 +597,7 @@ public:
setObjectCacheHint((Options::CacheHintOptions)cacheOptions);
registry->setOptions(options);
registry->getOrCreateSharedStateManager()->
setShareMode(SharedStateManager::SHARE_STATESETS);
setShareMode(SharedStateManager::SHARE_ALL);
registry->setReadFileCallback(ModelRegistry::instance());
}
};
@@ -520,9 +687,79 @@ typedef ModelRegistryCallback<OBJProcessPolicy,
OSGSubstitutePolicy, BuildLeafBVHPolicy>
OBJCallback;
// we get optimal geometry from the loader (Hah!).
struct IVEOptimizePolicy : public OptimizeModelPolicy {
IVEOptimizePolicy(const string& extension) :
OptimizeModelPolicy(extension)
{
_osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
}
Node* optimize(Node* node, const string& fileName,
const Options* opt)
{
ref_ptr<Node> optimized
= OptimizeModelPolicy::optimize(node, fileName, opt);
Group* group = dynamic_cast<Group*>(optimized.get());
MatrixTransform* transform
= dynamic_cast<MatrixTransform*>(optimized.get());
if (((transform && transform->getMatrix().isIdentity()) || group)
&& group->getName().empty()
&& group->getNumChildren() == 1) {
optimized = static_cast<Node*>(group->getChild(0));
group = dynamic_cast<Group*>(optimized.get());
if (group && group->getName().empty()
&& group->getNumChildren() == 1)
optimized = static_cast<Node*>(group->getChild(0));
}
const SGReaderWriterOptions* sgopt
= dynamic_cast<const SGReaderWriterOptions*>(opt);
if (sgopt && sgopt->getInstantiateMaterialEffects()) {
optimized = instantiateMaterialEffects(optimized.get(), sgopt);
}
else if (sgopt && sgopt->getInstantiateEffects()) {
optimized = instantiateEffects(optimized.get(), sgopt);
}
return optimized.release();
}
};
struct IVEProcessPolicy {
IVEProcessPolicy(const string& extension) {}
Node* process(Node* node, const string& filename,
const 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);
return root;
}
};
typedef ModelRegistryCallback<IVEProcessPolicy, DefaultCachePolicy,
IVEOptimizePolicy,
OSGSubstitutePolicy, BuildLeafBVHPolicy>
IVECallback;
namespace
{
ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
ModelRegistryCallbackProxy<OBJCallback> g_objRegister("obj");
ModelRegistryCallbackProxy<IVECallback> g_iveRegister("ive");
ModelRegistryCallbackProxy<IVECallback> g_osgtRegister("osgt");
ModelRegistryCallbackProxy<IVECallback> g_osgbRegister("osgb");
}

View File

@@ -169,6 +169,7 @@ SGModelLib::loadDeferredModel(const string &path, SGPropertyNode *prop_root,
osg::PagedLOD*
SGModelLib::loadPagedModel(SGPropertyNode *prop_root, SGModelData *data, SGModelLOD model_lods)
{
unsigned int simple_models = 0;
osg::PagedLOD *plod = new osg::PagedLOD;
osg::ref_ptr<SGReaderWriterOptions> opt;
@@ -189,15 +190,15 @@ SGModelLib::loadPagedModel(SGPropertyNode *prop_root, SGModelData *data, SGModel
plod->setMinimumExpiryTime(i, prop_root->getDoubleValue("/sim/rendering/plod-minimum-expiry-time-secs", 180.0 ) );
std::string lext = SGPath(lod.path).lower_extension();
// We can only have one set of ReaderWriterOptions for the PagedLOD, so
// we will just have to assume that if one of the defined models can
// handle instantiated effects, then the other can as well.
if ((lext == "ac") || (lext == "obj")) {
opt->setInstantiateEffects(true);
simple_models++;
}
}
// If all we have are simple models, then we can instantiate effects in
// the loader.
if (simple_models == model_lods.getNumLODs()) opt->setInstantiateEffects(true);
plod->setDatabaseOptions(opt.get());
return plod;

View File

@@ -50,6 +50,8 @@
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/tgdb/apt_signs.hxx>
#include <simgear/scene/tgdb/obj.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/tgdb/SGBuildingBin.hxx>
#include "SGOceanTile.hxx"
@@ -59,6 +61,7 @@
#define ROAD_DETAILED "OBJECT_ROAD_DETAILED"
#define RAILWAY_ROUGH "OBJECT_RAILWAY_ROUGH"
#define RAILWAY_DETAILED "OBJECT_RAILWAY_DETAILED"
#define BUILDING_LIST "BUILDING_LIST"
namespace simgear {
@@ -121,6 +124,12 @@ struct ReaderWriterSTG::_ModelBin {
double _hdg;
int _size;
};
struct _BuildingList {
_BuildingList() : _lon(0), _lat(0), _elev(0) { }
std::string _filename;
std::string _material_name;
double _lon, _lat, _elev;
};
class DelayLoadReadFileCallback : public OptionsReadFileCallback {
@@ -185,8 +194,6 @@ struct ReaderWriterSTG::_ModelBin {
};
typedef QuadTreeBuilder<osg::LOD*, _ObjectStatic, MakeQuadLeaf, AddModelLOD,
GetModelLODCoord> STGObjectsQuadtree;
public:
virtual osgDB::ReaderWriter::ReadResult
readNode(const std::string&, const osgDB::Options*)
@@ -203,12 +210,40 @@ struct ReaderWriterSTG::_ModelBin {
if (signBuilder.getSignsGroup())
group->addChild(signBuilder.getSignsGroup());
if (_buildingList.size() > 0) {
SGMaterialLibPtr matlib = _options->getMaterialLib();
bool useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
if (!matlib) {
SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings");
} else {
for (std::list<_BuildingList>::iterator i = _buildingList.begin(); i != _buildingList.end(); ++i) {
// Build buildings for each list of buildings
SGGeod geodPos = SGGeod::fromDegM(i->_lon, i->_lat, 0.0);
SGMaterial* mat = matlib->find(i->_material_name, geodPos);
SGPath path = SGPath(i->_filename);
SGBuildingBin* buildingBin = new SGBuildingBin(path, mat, useVBOs);
SGBuildingBinList buildingBinList;
buildingBinList.push_back(buildingBin);
osg::MatrixTransform* matrixTransform;
matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev)));
matrixTransform->setName("rotateBuildings");
matrixTransform->setDataVariance(osg::Object::STATIC);
matrixTransform->addChild(createRandomBuildings(buildingBinList, osg::Matrix::identity(), _options));
group->addChild(matrixTransform);
}
}
}
return group.release();
}
mt _seed;
std::list<_ObjectStatic> _objectStaticList;
std::list<_Sign> _signList;
std::list<_BuildingList> _buildingList;
/// The original options to use for this bunch of models
osg::ref_ptr<SGReaderWriterOptions> _options;
@@ -315,7 +350,6 @@ struct ReaderWriterSTG::_ModelBin {
// starting with 2018.3 we will use deltas rather than absolutes as it is more intuitive for the user
// and somewhat easier to visualise
double detailedRange = atof(options->getPluginStringData("SimGear::LOD_RANGE_DETAILED").c_str());
double bareRangeDelta = atof(options->getPluginStringData("SimGear::LOD_RANGE_BARE").c_str());
double roughRangeDelta = atof(options->getPluginStringData("SimGear::LOD_RANGE_ROUGH").c_str());
@@ -490,6 +524,13 @@ struct ReaderWriterSTG::_ModelBin {
obj._options = opt;
checkInsideBucket(absoluteFileName, obj._lon, obj._lat);
_objectStaticList.push_back(obj);
} else if (token == BUILDING_LIST) {
_BuildingList buildinglist;
buildinglist._filename = path.local8BitStr();
in >> buildinglist._material_name >> buildinglist._lon >> buildinglist._lat >> buildinglist._elev;
checkInsideBucket(absoluteFileName, buildinglist._lon, buildinglist._lat);
_buildingListList.push_back(buildinglist);
//SG_LOG(SG_TERRAIN, SG_ALERT, "Building list: " << buildinglist._filename << " " << buildinglist._material_name << " " << buildinglist._lon << " " << buildinglist._lat);
} else {
SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName
<< ": Unknown token '" << token << "'" );
@@ -560,6 +601,7 @@ struct ReaderWriterSTG::_ModelBin {
// we just need to know about the read file callback that itself holds the data
osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
readFileCallback->_objectStaticList = _objectStaticList;
readFileCallback->_buildingList = _buildingListList;
readFileCallback->_signList = _signList;
readFileCallback->_options = options;
readFileCallback->_bucket = bucket;
@@ -584,6 +626,7 @@ struct ReaderWriterSTG::_ModelBin {
std::list<_Object> _objectList;
std::list<_ObjectStatic> _objectStaticList;
std::list<_Sign> _signList;
std::list<_BuildingList> _buildingListList;
};
ReaderWriterSTG::ReaderWriterSTG()

View File

@@ -44,12 +44,11 @@
#include <osgDB/FileUtils>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/math/SGLimits.hxx>
#include <simgear/math/SGMisc.hxx>
#include <simgear/math/sg_random.h>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/material/Effect.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/model/model.hxx>
#include <simgear/props/props.hxx>
@@ -107,6 +106,59 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const
return bb;
}
// Set up a BuildingBin from a file containing a list of individual building
// positions.
SGBuildingBin::SGBuildingBin(const SGPath& absoluteFileName, const SGMaterial *mat, bool useVBOs) :
SGBuildingBin::SGBuildingBin(mat, useVBOs)
{
if (!absoluteFileName.exists()) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Building list file " << absoluteFileName << " does not exist.");
return;
}
sg_gzifstream stream(absoluteFileName);
if (!stream.is_open()) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open " << absoluteFileName << " does not exist.");
return;
}
while (!stream.eof()) {
// read a line. Each line defines a single builing position, and may have
// a comment, starting with #
std::string line;
std::getline(stream, line);
// strip comments
std::string::size_type hash_pos = line.find('#');
if (hash_pos != std::string::npos)
line.resize(hash_pos);
// and process further
std::stringstream in(line);
// Line format is X Y Z R T
// where:
// X,Y,Z are the cartesian coordinates of the bottom SW corner of the building. +X is East, +Y is North
// R is the building rotation in degrees centered on the SW corner
// T is the building type [0, 1, 2] for SMALL, MEDIUM, LARGE
float x, y, z, r;
int t;
in >> x >> y >> z >> r >> t;
//SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << t );
SGVec3f p = SGVec3f(x,y,z);
BuildingType type = BuildingType::SMALL;
if (t == 1) type = BuildingType::MEDIUM;
if (t == 2) type = BuildingType::LARGE;
// Rotation is in the file as degrees, but in the datastructure normalized
// to 0.0 - 1.0
insert(p, (float) (r / 360.0f), type);
}
stream.close();
};
// Set up the building set based on the material definitions
SGBuildingBin::SGBuildingBin(const SGMaterial *mat, bool useVBOs) {
@@ -165,7 +217,7 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const
if (useVBOs) {
sharedGeometry->setUseVertexBufferObjects(true);
}
for (unsigned int j = 0; j < BUILDING_SET_SIZE; j++) {
float width;
float depth;
@@ -820,8 +872,8 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const
};
// This actually returns a MatrixTransform node. If we rotate the whole
// forest into the local Z-up coordinate system we can reuse the
// primitive building geometry for all the forests of the same type.
// set of buildings into the local Z-up coordinate system we can reuse the
// primitive building geometry for all the buildings of the same type.
osg::Group* createRandomBuildings(SGBuildingBinList& buildings, const osg::Matrix& transform,
const SGReaderWriterOptions* options)
{

View File

@@ -36,10 +36,12 @@
#include <osg/Material>
#include <osg/CullFace>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/material/Effect.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/util/QuadTreeBuilder.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
#include <simgear/scene/util/StateAttributeFactory.hxx>
@@ -169,6 +171,7 @@ private:
public:
SGBuildingBin(const SGMaterial *mat, bool useVBOs);
SGBuildingBin(const SGPath& absoluteFileName, const SGMaterial *mat, bool useVBOs);
~SGBuildingBin() {
smallBuildings.clear();

View File

@@ -230,9 +230,9 @@ void addTreeToLeafGeode(Geode* geode, const SGVec3f& p, const SGVec3f& t)
posArray->insert(posArray->end(), 4, pos);
size_t numVerts = posArray->size();
int imax = 2;
unsigned int imax = 2;
if (use_tree_shadows) { imax = 3; }
for (int i = 0; i < imax; ++i) {
for (unsigned int i = 0; i < imax; ++i) {
if (i < geom->getNumPrimitiveSets()) {
DrawArrays* primSet = static_cast<DrawArrays*>(geom->getPrimitiveSet(i));
if (primSet != nullptr)

View File

@@ -16,6 +16,7 @@ set(HEADERS
RenderConstants.hxx
SGDebugDrawCallback.hxx
SGEnlargeBoundingBox.hxx
SGImageUtils.hxx
SGNodeMasks.hxx
SGPickCallback.hxx
SGReaderWriterOptions.hxx
@@ -44,6 +45,7 @@ set(SOURCES
PrimitiveUtils.cxx
QuadTreeBuilder.cxx
SGEnlargeBoundingBox.cxx
SGImageUtils.cxx
SGReaderWriterOptions.cxx
SGSceneFeatures.cxx
SGSceneUserData.cxx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,542 @@
/* -*-c++-*- */
/* ImageUtils: copied from osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2018 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef SIMGEAR_IMAGEUTILS_H
#define SIMGEAR_IMAGEUTILS_H
#include <osg/Image>
#include <osg/Texture>
#include <osg/GL>
#include <osg/NodeVisitor>
#include <osgDB/ReaderWriter>
#include <vector>
//These formats were not added to OSG until after 2.8.3 so we need to define them to use them.
#ifndef GL_EXT_texture_compression_rgtc
#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB
#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC
#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD
#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE
#endif
#ifndef GL_IMG_texture_compression_pvrtc
#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
#endif
namespace simgear
{
class ImageUtils
{
public:
/**
* Clones an image.
*
* Use this instead of the osg::Image copy construtor, which keeps the referenced to
* its underlying BufferObject around. Calling dirty() on the new clone appears to
* help, but just call this method instead to be sure.
*/
static osg::Image* cloneImage(const osg::Image* image);
/**
* Tweaks an image for consistency. OpenGL allows enums like "GL_RGBA" et.al. to be
* used in the internal texture format, when really "GL_RGBA8" is the proper things
* to use. This method accounts for that. Some parts of osgEarth (like the texture-
* array compositor) rely on the internal texture format being correct.
* (http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf)
*/
static void fixInternalFormat(osg::Image* image);
/**
* Marks an image as containing un-normalized data values.
*
* Normally the values in an image are "normalized", i.e. scaled so they are in the
* range [0..1]. This is normal for color values. But when the image is being used
* for coverage data (a value lookup table) it is desireable to store the raw
* values instead.
*/
static void markAsUnNormalized(osg::Image* image, bool value);
/** Inverse of above. */
static void markAsNormalized(osg::Image* image, bool value) { markAsUnNormalized(image, !value); }
/**
* Whether the image has been marked as containing un-normalized values.
*/
static bool isUnNormalized(const osg::Image* image);
/**
* Whether the image has been marked as containing normalized values.
*/
static bool isNormalized(const osg::Image* image) { return !isUnNormalized(image); }
/**
* Copys a portion of one image into another.
*/
static bool copyAsSubImage(
const osg::Image* src,
osg::Image* dst,
int dst_start_col, int dst_start_row);
/**
* Resizes an image. Returns a new image, leaving the input image unaltered.
*
* Note. If the output parameter is NULL, this method will allocate a new image and
* resize into that new image. If the output parameter is non-NULL, this method will
* assume that the output image is already allocated to the proper size, and will
* do a resize+copy into that image. In the latter case, it is your responsibility
* to make sure the output image is allocated to the proper size.
*
* If the output parameter is non-NULL, then the mipmapLevel is also considered.
* This lets you resize directly into a particular mipmap level of the output image.
*/
static bool resizeImage(
const osg::Image* input,
unsigned int new_s, unsigned int new_t,
osg::ref_ptr<osg::Image>& output,
unsigned int mipmapLevel = 0, bool bilinear = true);
/**
* Crops the input image to the dimensions provided and returns a
* new image. Returns a new image, leaving the input image unaltered.
* Note: The input destination bounds are modified to reflect the bounds of the
* actual output image. Due to the fact that you cannot crop in the middle of a pixel
* The specified destination extents and the output extents may vary slightly.
*@param src_minx
* The minimum x coordinate of the input image.
*@param src_miny
* The minimum y coordinate of the input image.
*@param src_maxx
* The maximum x coordinate of the input image.
*@param src_maxy
* The maximum y coordinate of the input image.
*@param dst_minx
* The desired minimum x coordinate of the cropped image.
*@param dst_miny
* The desired minimum y coordinate of the cropped image.
*@param dst_maxx
* The desired maximum x coordinate of the cropped image.
*@param dst_maxy
* The desired maximum y coordinate of the cropped image.
*/
static osg::Image* cropImage(
const osg::Image* image,
double src_minx, double src_miny, double src_maxx, double src_maxy,
double &dst_minx, double &dst_miny, double &dst_maxx, double &dst_maxy);
/**
* Creates an Image that "blends" two images into a new image in which "primary"
* occupies mipmap level 0, and "secondary" occupies all the other mipmap levels.
*
* WARNING: this method assumes that primary and seconday are the same exact size
* and the same exact format.
*/
static osg::Image* createMipmapBlendedImage(
const osg::Image* primary,
const osg::Image* secondary);
/**
* Creates a new image containing mipmaps built with nearest-neighbor
* sampling.
*/
static osg::Image* buildNearestNeighborMipmaps(
const osg::Image* image);
/**
* Blends the "src" image into the "dest" image, based on the "a" value.
* The two images must be the same.
*/
static bool mix(osg::Image* dest, const osg::Image* src, float a);
/**
* Creates and returns a copy of the input image after applying a
* sharpening filter. Returns a new image, leaving the input image unaltered.
*/
static osg::Image* createSharpenedImage(const osg::Image* image);
/**
* For each "layer" in the input image (each bitmap in the "r" dimension),
* create a new, separate image with r=1. If the input image is r=1, it is
* simply placed onto the output vector (no copy).
* Returns true upon sucess, false upon failure
*/
static bool flattenImage(osg::Image* image, std::vector<osg::ref_ptr<osg::Image> >& output);
/**
* Gets whether the input image's dimensions are powers of 2.
*/
static bool isPowerOfTwo(const osg::Image* image);
/**
* Gets a transparent, single pixel image used for a placeholder
*/
static osg::Image* createEmptyImage();
/**
* Gets a transparent image used for a placeholder with the specified dimensions
*/
static osg::Image* createEmptyImage(unsigned int s, unsigned int t);
/**
* Creates a one-pixel image.
*/
static osg::Image* createOnePixelImage(const osg::Vec4& color);
/**
* Tests an image to see whether it's "empty", i.e. completely transparent,
* within an alpha threshold.
*/
static bool isEmptyImage(const osg::Image* image, float alphaThreshold = 0.01);
/**
* Tests an image to see whether it's "single color", i.e. completely filled with a single color,
* within an threshold (threshold is tested on each channel).
*/
static bool isSingleColorImage(const osg::Image* image, float threshold = 0.01);
/**
* Returns true if it is possible to convert the image to the specified
* format/datatype specification.
*/
static bool canConvert(const osg::Image* image, GLenum pixelFormat, GLenum dataType);
/**
* Converts an image to the specified format.
*/
static osg::Image* convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType);
/**
*Converts the given image to RGB8
*/
static osg::Image* convertToRGB8(const osg::Image* image);
/**
*Converts the given image to RGBA8
*/
static osg::Image* convertToRGBA8(const osg::Image* image);
/**
* True if the two images are of the same format (pixel format, data type, etc.)
* though not necessarily the same size, depth, etc.
*/
static bool sameFormat(const osg::Image* lhs, const osg::Image* rhs);
/**
* True if the two images have the same format AND size, and can therefore
* be used together in a texture array.
*/
static bool textureArrayCompatible(const osg::Image* lhs, const osg::Image* rhs);
/**
*Compares the image data of two images and determines if they are equivalent
*/
static bool areEquivalent(const osg::Image *lhs, const osg::Image *rhs);
/**
* Whether two colors are roughly equivalent.
*/
static bool areRGBEquivalent(const osg::Vec4& lhs, const osg::Vec4& rhs, float epsilon = 0.01f) {
return
fabs(lhs.r() - rhs.r()) < epsilon &&
fabs(lhs.g() - rhs.g()) < epsilon &&
fabs(lhs.b() - rhs.b()) < epsilon;
}
/**
* Checks whether the image has an alpha component
*/
static bool hasAlphaChannel(const osg::Image* image);
/**
* Checks whether an image has transparency; i.e. whether
* there are any pixels with an alpha component whole value
* falls below the specified threshold.
*/
static bool hasTransparency(const osg::Image* image, float alphaThreshold = 1.0f);
/**
* Finds pixels with alpha less than [maxAlpha] and sets their color
* to match that or neighboring non-alpha pixels. This facilitates multipass
* blending or abutting tiles by overlapping them slightly. Specify "maxAlpha"
* as the maximum value to consider when searching for fully-transparent pixels.
*
* Returns false if there is no reader or writer for the image's format.
*/
static bool featherAlphaRegions(osg::Image* image, float maxAlpha = 0.0f);
/**
* Converts an image (in place) to premultiplied-alpha format.
* Returns False is the conversion fails, e.g., if there is no reader
* or writer for the image format.
*/
static bool convertToPremultipliedAlpha(osg::Image* image);
/**
* Checks whether the given image is compressed
*/
static bool isCompressed(const osg::Image* image);
/**
* Generated a bump map image for the input image
*/
static osg::Image* createBumpMap(const osg::Image* input);
/**
* Is it a floating-point texture format?
*/
static bool isFloatingPointInternalFormat(GLint internalFormat);
/**
* Compute a texture compression format suitable for the image.
*/
static bool computeTextureCompressionMode(
const osg::Image* image,
osg::Texture::InternalFormatMode& out_mode);
/**
* Bicubic upsampling in a quadrant. Target image is already allocated.
*/
static bool bicubicUpsample(
const osg::Image* source,
osg::Image* target,
unsigned quadrant,
unsigned stride);
/**
*
*/
static osg::Image* upSampleNN(const osg::Image* src, int quadrant);
/**
* Activates mipmapping for a texture image if the correct filters exist.
*
* If OSG has an ImageProcessor service installed, this method will use that
* to generate mipmaps. If not, the method will be a NOP and the GPU wil
* generate mipmaps (if necessary) upon GPU transfer.
*/
static void activateMipMaps(osg::Texture* texture);
/**
* Gets an osgDB::ReaderWriter for the given input stream.
* Returns NULL if no ReaderWriter can be found.
*/
static osgDB::ReaderWriter* getReaderWriterForStream(std::istream& stream);
/**
* Reads an osg::Image from the given input stream.
* Returns NULL if the image could not be read.
*/
static osg::Image* readStream(std::istream& stream, const osgDB::Options* options);
/**
* Reads color data out of an image, regardles of its internal pixel format.
*/
class PixelReader
{
public:
/**
* Constructs a pixel reader. "Normalized" means that the values in the source
* image have been scaled to [0..1] and should be denormalized upon reading.
*/
PixelReader(const osg::Image* image);
/** Sets an image to read. */
void setImage(const osg::Image* image);
/** Whether to use bilinear interpolation when reading with u,v coords (default=true) */
void setBilinear(bool value) { _bilinear = value; }
/** Whether PixelReader supports a given format/datatype combiniation. */
static bool supports(GLenum pixelFormat, GLenum dataType);
/** Whether PixelReader can read from the specified image. */
static bool supports(const osg::Image* image) {
return image && supports(image->getPixelFormat(), image->getDataType());
}
/** Reads a color from the image */
osg::Vec4 operator()(int s, int t, int r = 0, int m = 0) const {
return (*_reader)(this, s, t, r, m);
}
/** Reads a color from the image */
osg::Vec4 operator()(unsigned s, unsigned t, unsigned r = 0, int m = 0) const {
return (*_reader)(this, s, t, r, m);
}
/** Reads a color from the image by unit coords [0..1] */
osg::Vec4 operator()(float u, float v, int r = 0, int m = 0) const;
osg::Vec4 operator()(double u, double v, int r = 0, int m = 0) const;
// internals:
const unsigned char* data(int s = 0, int t = 0, int r = 0, int m = 0) const {
return m == 0 ?
_image->data() + s*_colMult + t*_rowMult + r*_imageSize :
_image->getMipmapData(m) + s*_colMult + t*(_rowMult >> m) + r*(_imageSize >> m);
}
typedef osg::Vec4(*ReaderFunc)(const PixelReader* ia, int s, int t, int r, int m);
ReaderFunc _reader;
const osg::Image* _image;
unsigned _colMult;
unsigned _rowMult;
unsigned _imageSize;
bool _normalized;
bool _bilinear;
};
/**
* Writes color data to an image, regardles of its internal pixel format.
*/
class PixelWriter
{
public:
/**
* Constructs a pixel writer. "Normalized" means the values are scaled to [0..1]
* before writing.
*/
PixelWriter(osg::Image* image);
/** Whether PixelWriter can write to an image with the given format/datatype combo. */
static bool supports(GLenum pixelFormat, GLenum dataType);
/** Whether PixelWriter can write to non-const version of an image. */
static bool supports(const osg::Image* image) {
return image && supports(image->getPixelFormat(), image->getDataType());
}
/** Writes a color to a pixel. */
void operator()(const osg::Vec4& c, int s, int t, int r = 0, int m = 0) {
(*_writer)(this, c, s, t, r, m);
}
void f(const osg::Vec4& c, float s, float t, int r = 0, int m = 0) {
this->operator()(c,
(int)(s * (float)(_image->s() - 1)),
(int)(t * (float)(_image->t() - 1)),
r, m);
}
// internals:
osg::Image* _image;
unsigned _colMult;
unsigned _rowMult;
unsigned _imageSize;
bool _normalized;
unsigned char* data(int s = 0, int t = 0, int r = 0, int m = 0) const {
return m == 0 ?
_image->data() + s*_colMult + t*_rowMult + r*_imageSize :
_image->getMipmapData(m) + s*_colMult + t*(_rowMult >> m) + r*(_imageSize >> m);
}
typedef void(*WriterFunc)(const PixelWriter* iw, const osg::Vec4& c, int s, int t, int r, int m);
WriterFunc _writer;
};
/**
* Functor that visits every pixel in an image
*/
template<typename T>
struct PixelVisitor : public T
{
/**
* Traverse an image, and call this method on the superclass:
*
* bool operator(osg::Vec4& pixel);
*
* If that method returns true, write the value back at the same location.
*/
void accept(osg::Image* image) {
PixelReader _reader(image);
PixelWriter _writer(image);
for (int r = 0; r<image->r(); ++r) {
for (int t = 0; t<image->t(); ++t) {
for (int s = 0; s<image->s(); ++s) {
osg::Vec4f pixel = _reader(s, t, r);
if ((*this)(pixel))
_writer(pixel, s, t, r);
}
}
}
}
/**
* Traverse an image, and call this method on the superclass:
*
* bool operator(const osg::Vec4& srcPixel, osg::Vec4& destPixel);
*
* If that method returns true, write destPixel back at the same location
* in the destination image.
*/
void accept(const osg::Image* src, osg::Image* dest) {
PixelReader _readerSrc(src);
PixelReader _readerDest(dest);
PixelWriter _writerDest(dest);
for (int r = 0; r<src->r(); ++r) {
for (int t = 0; t<src->t(); ++t) {
for (int s = 0; s<src->s(); ++s) {
const osg::Vec4f pixelSrc = _readerSrc(s, t, r);
osg::Vec4f pixelDest = _readerDest(s, t, r);
if ((*this)(pixelSrc, pixelDest))
_writerDest(pixelDest, s, t, r);
}
}
}
}
};
/**
* Simple functor to copy pixels from one image to another.
*
* Usage:
* PixelVisitor<CopyImage>().accept( fromImage, toImage );
*/
struct CopyImage {
bool operator()(const osg::Vec4f& src, osg::Vec4f& dest) {
dest = src;
return true;
}
};
};
/** Visitor that finds and operates on textures and images */
class TextureAndImageVisitor : public osg::NodeVisitor
{
public:
TextureAndImageVisitor();
virtual ~TextureAndImageVisitor() { }
public:
/** Visits a texture and, by default, all its components images */
virtual void apply(osg::Texture& texture);
/** Visits an image inside a texture */
virtual void apply(osg::Image& image) { }
public: // osg::NodeVisitor
virtual void apply(osg::Node& node);
virtual void apply(osg::StateSet& stateSet);
};
}
#endif //SIMGEAR_IMAGEUTILS_H

View File

@@ -43,7 +43,11 @@ SGSceneFeatures::SGSceneFeatures() :
_shaderLights(true),
_pointSpriteLights(true),
_distanceAttenuationLights(true),
_textureFilter(1)
_textureFilter(1),
_MaxTextureSize(4096),
_TextureCacheCompressionActive(true),
_TextureCacheCompressionActiveTransparent(true),
_TextureCacheActive(false)
{
}

View File

@@ -1,102 +1,133 @@
/* -*-c++-*-
*
* Copyright (C) 2006-2007 Mathias Froehlich
*
* 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.
*
*/
*
* Copyright (C) 2006-2007 Mathias Froehlich
*
* 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.
*
*/
#ifndef SG_SCENE_FEATURES_HXX
#define SG_SCENE_FEATURES_HXX
#include <simgear/structure/SGReferenced.hxx>
#include <string>
#include <simgear/misc/sg_path.hxx>
namespace osg { class Texture; }
class SGSceneFeatures : public SGReferenced {
public:
static SGSceneFeatures* instance();
static SGSceneFeatures* instance();
enum TextureCompression {
DoNotUseCompression,
UseARBCompression,
UseDXT1Compression,
UseDXT3Compression,
UseDXT5Compression
};
enum TextureCompression {
DoNotUseCompression,
UseARBCompression,
UseDXT1Compression,
UseDXT3Compression,
UseDXT5Compression
};
int getMaxTextureSize() const { return _MaxTextureSize; }
void setMaxTextureSize(const int maxTextureSize) { _MaxTextureSize = maxTextureSize; }
void setTextureCompression(TextureCompression textureCompression)
{ _textureCompression = textureCompression; }
TextureCompression getTextureCompression() const
{ return _textureCompression; }
void setTextureCompression(osg::Texture* texture) const;
SGPath getTextureCompressionPath() const { return _TextureCompressionPath; }
void setTextureCompressionPath(const SGPath path) { _TextureCompressionPath = path; }
void setEnablePointSpriteLights(bool enable)
{ _pointSpriteLights = enable; }
bool getEnablePointSpriteLights() const
{
return _pointSpriteLights;
}
bool getEnablePointSpriteLights(unsigned contextId) const
{
if (!_pointSpriteLights)
return false;
return getHavePointSprites(contextId);
}
bool getTextureCacheActive() const { return _TextureCacheActive; }
void setTextureCacheActive(const bool val) { _TextureCacheActive = val; }
void setEnableDistanceAttenuationLights(bool enable)
{ _distanceAttenuationLights = enable; }
bool getEnableDistanceAttenuationLights(unsigned contextId) const
{
if (!_distanceAttenuationLights)
return false;
return getHavePointParameters(contextId);
}
bool getTextureCacheCompressionActive() const { return _TextureCacheCompressionActive; }
void setTextureCacheCompressionActive(const bool val) { _TextureCacheCompressionActive = val; }
void setEnableShaderLights(bool enable)
{ _shaderLights = enable; }
bool getEnableShaderLights(unsigned contextId) const
{
if (!_shaderLights)
return false;
return getHaveShaderPrograms(contextId);
}
void setTextureFilter(int max)
{ _textureFilter = max; }
int getTextureFilter() const
{ return _textureFilter; }
bool getTextureCacheCompressionActiveTransparent() const { return _TextureCacheCompressionActiveTransparent; }
void setTextureCacheCompressionActiveTransparent(const bool val) { _TextureCacheCompressionActiveTransparent = val; }
void setTextureCompression(TextureCompression textureCompression) { _textureCompression = textureCompression; }
TextureCompression getTextureCompression() const { return _textureCompression; }
// modify the texture compression on the texture parameter
void setTextureCompression(osg::Texture* texture) const;
void setEnablePointSpriteLights(bool enable)
{
_pointSpriteLights = enable;
}
bool getEnablePointSpriteLights() const
{
return _pointSpriteLights;
}
bool getEnablePointSpriteLights(unsigned contextId) const
{
if (!_pointSpriteLights)
return false;
return getHavePointSprites(contextId);
}
void setEnableDistanceAttenuationLights(bool enable)
{
_distanceAttenuationLights = enable;
}
bool getEnableDistanceAttenuationLights(unsigned contextId) const
{
if (!_distanceAttenuationLights)
return false;
return getHavePointParameters(contextId);
}
void setEnableShaderLights(bool enable)
{
_shaderLights = enable;
}
bool getEnableShaderLights(unsigned contextId) const
{
if (!_shaderLights)
return false;
return getHaveShaderPrograms(contextId);
}
void setTextureFilter(int max)
{
_textureFilter = max;
}
int getTextureFilter() const
{
return _textureFilter;
}
protected:
bool getHavePointSprites(unsigned contextId) const;
bool getHaveFragmentPrograms(unsigned contextId) const;
bool getHaveVertexPrograms(unsigned contextId) const;
bool getHaveShaderPrograms(unsigned contextId) const;
bool getHavePointParameters(unsigned contextId) const;
bool getHavePointSprites(unsigned contextId) const;
bool getHaveFragmentPrograms(unsigned contextId) const;
bool getHaveVertexPrograms(unsigned contextId) const;
bool getHaveShaderPrograms(unsigned contextId) const;
bool getHavePointParameters(unsigned contextId) const;
private:
SGSceneFeatures();
SGSceneFeatures(const SGSceneFeatures&);
SGSceneFeatures& operator=(const SGSceneFeatures&);
SGSceneFeatures();
SGSceneFeatures(const SGSceneFeatures&);
SGSceneFeatures& operator=(const SGSceneFeatures&);
TextureCompression _textureCompression;
bool _shaderLights;
bool _pointSpriteLights;
bool _distanceAttenuationLights;
int _textureFilter;
TextureCompression _textureCompression;
int _MaxTextureSize;
SGPath _TextureCompressionPath;
bool _TextureCacheCompressionActive;
bool _TextureCacheCompressionActiveTransparent;
bool _TextureCacheActive;
bool _shaderLights;
bool _pointSpriteLights;
bool _distanceAttenuationLights;
int _textureFilter;
};
#endif