diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index 0c31354b..d5c32af7 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -48,6 +48,8 @@ #include #include #include +#include +#include #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H #include "version.h" @@ -123,6 +125,9 @@ Client::Client() : setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION)); static bool didInitCurlGlobal = false; + static SGMutex initMutex; + + SGGuard g(initMutex); if (!didInitCurlGlobal) { curl_global_init(CURL_GLOBAL_ALL); didInitCurlGlobal = true; @@ -476,12 +481,26 @@ size_t Client::requestReadCallback(char *ptr, size_t size, size_t nmemb, void *u return actualBytes; } +bool isRedirectStatus(int code) +{ + return ((code >= 300) && (code < 400)); +} + size_t Client::requestHeaderCallback(char *rawBuffer, size_t size, size_t nitems, void *userdata) { size_t byteSize = size * nitems; Request* req = static_cast(userdata); std::string h = strutils::simplify(std::string(rawBuffer, byteSize)); + if (req->readyState() >= HTTP::Request::HEADERS_RECEIVED) { + // this can happen with chunked transfers (secondary chunks) + // or redirects + if (isRedirectStatus(req->responseCode())) { + req->responseStart(h); + return byteSize; + } + } + if (req->readyState() == HTTP::Request::OPENED) { req->responseStart(h); return byteSize; diff --git a/simgear/io/HTTPRequest.cxx b/simgear/io/HTTPRequest.cxx index 294d2f0c..7266d780 100644 --- a/simgear/io/HTTPRequest.cxx +++ b/simgear/io/HTTPRequest.cxx @@ -328,6 +328,16 @@ unsigned int Request::responseLength() const return _responseLength; } +//------------------------------------------------------------------------------ +void Request::setSuccess(int code) +{ + _responseStatus = code; + _responseReason.clear(); + if( !isComplete() ) { + setReadyState(DONE); + } +} + //------------------------------------------------------------------------------ void Request::setFailure(int code, const std::string& reason) { diff --git a/simgear/io/HTTPRequest.hxx b/simgear/io/HTTPRequest.hxx index 0def0888..9ba8db3e 100644 --- a/simgear/io/HTTPRequest.hxx +++ b/simgear/io/HTTPRequest.hxx @@ -224,7 +224,7 @@ protected: virtual void onAlways(); void setFailure(int code, const std::string& reason); - + void setSuccess(int code); private: friend class Client; friend class Connection; diff --git a/simgear/io/test_HTTP.cxx b/simgear/io/test_HTTP.cxx index 3bf9947d..ccef3f9e 100644 --- a/simgear/io/test_HTTP.cxx +++ b/simgear/io/test_HTTP.cxx @@ -273,7 +273,23 @@ public: d << "\r\n"; // final CRLF to terminate the headers d << contentStr; push(d.str().c_str()); - + } else if (path == "/test_redirect") { + string contentStr("See Here"); + stringstream d; + d << "HTTP/1.1 " << 302 << " " << "Found" << "\r\n"; + d << "Location:" << " http://localhost:2000/was_redirected" << "\r\n"; + d << "Content-Length:" << contentStr.size() << "\r\n"; + d << "\r\n"; // final CRLF to terminate the headers + d << contentStr; + push(d.str().c_str()); + } else if (path == "/was_redirected") { + string contentStr(BODY1); + stringstream d; + d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n"; + d << "Content-Length:" << contentStr.size() << "\r\n"; + d << "\r\n"; // final CRLF to terminate the headers + d << contentStr; + push(d.str().c_str()); } else { TestServerChannel::processRequestHeaders(); } @@ -773,6 +789,24 @@ cout << "testing proxy close" << endl; SG_CHECK_EQUAL(tr2->bodyData, string(BODY1)); SG_CHECK_EQUAL(tr2->responseBytesReceived(), strlen(BODY1)); } + + { + cout << "redirect test" << endl; + // redirect test + testServer.disconnectAll(); + cl.clearAllConnections(); + + TestRequest* tr = new TestRequest("http://localhost:2000/test_redirect"); + HTTP::Request_ptr own(tr); + cl.makeRequest(tr); + + waitForComplete(&cl, tr); + SG_CHECK_EQUAL(tr->responseCode(), 200); + SG_CHECK_EQUAL(tr->responseReason(), string("OK")); + SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1)); + SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY1)); + SG_CHECK_EQUAL(tr->bodyData, string(BODY1)); + } cout << "all tests passed ok" << endl; return EXIT_SUCCESS; diff --git a/simgear/io/test_HTTP.hxx b/simgear/io/test_HTTP.hxx index 3cb925c0..f8dde98c 100644 --- a/simgear/io/test_HTTP.hxx +++ b/simgear/io/test_HTTP.hxx @@ -30,7 +30,6 @@ public: virtual ~TestServerChannel() { - std::cerr << "dtor test server channel" << std::endl; } virtual void collectIncomingData(const char* s, int n) @@ -139,8 +138,8 @@ public: void sendErrorResponse(int code, bool close, std::string content) { - std::cerr << "sending error " << code << " for " << path << std::endl; - std::cerr << "\tcontent:" << content << std::endl; + // std::cerr << "sending error " << code << " for " << path << std::endl; + // std::cerr << "\tcontent:" << content << std::endl; std::stringstream headerData; headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n"; @@ -168,7 +167,6 @@ public: virtual void handleClose (void) { - std::cerr << "channel close" << std::endl; NetBufferChannel::handleClose(); } diff --git a/simgear/misc/strutils.cxx b/simgear/misc/strutils.cxx index 88bf2d21..6d2cbeaa 100644 --- a/simgear/misc/strutils.cxx +++ b/simgear/misc/strutils.cxx @@ -623,6 +623,22 @@ namespace simgear { *p = tolower(*p); } } + + +bool iequals(const std::string& a, const std::string& b) +{ + const auto lenA = a.length(); + const auto lenB = b.length(); + if (lenA != lenB) return false; + + const char* aPtr = a.data(); + const char* bPtr = b.data(); + for (size_t i = 0; i < lenA; ++i) { + if (tolower(*aPtr++) != tolower(*bPtr++)) return false; + } + + return true; +} #if defined(SG_WINDOWS) static std::wstring convertMultiByteToWString(DWORD encoding, const std::string& a) @@ -991,6 +1007,16 @@ std::string unescape(const char* s) } return r; } +std::string replace(std::string source, const std::string search, const std::string replacement, std::size_t start_pos) +{ + if (start_pos < source.length()) { + while ((start_pos = source.find(search, start_pos)) != std::string::npos) { + source.replace(start_pos, search.length(), replacement); + start_pos += replacement.length(); + } + } + return source; +} string sanitizePrintfFormat(const string& input) { diff --git a/simgear/misc/strutils.hxx b/simgear/misc/strutils.hxx index 74fb7f39..a289b657 100644 --- a/simgear/misc/strutils.hxx +++ b/simgear/misc/strutils.hxx @@ -264,6 +264,11 @@ namespace simgear { */ void lowercase(std::string &s); + /** + * case-insensitive string comparisom + */ + bool iequals(const std::string& a, const std::string& b); + /** * convert a string in the local Windows 8-bit encoding to UTF-8 * (no-op on other platforms) @@ -328,7 +333,18 @@ namespace simgear { inline std::string unescape(const std::string& str) { return unescape(str.c_str()); } - /** + /** + * Replace matching elements of string. + * + * @param source source string + * @param search search string + * @param replace replacement string + * @param start_pos starting position for replacement in source. Checked to ensure less than length of source. + * @return string with all occurrences of search changed to replace + */ + std::string replace(std::string source, const std::string search, const std::string replacement, std::size_t start_pos = 0); + + /** * Check a printf-style format string for dangerous (buffer-overflowing, * memory re-writing) format tokens. If a problematic token is * found, logs an error (SG_WARN) and returns an empty format string. diff --git a/simgear/misc/strutils_test.cxx b/simgear/misc/strutils_test.cxx index d9559ad3..b78b9a63 100644 --- a/simgear/misc/strutils_test.cxx +++ b/simgear/misc/strutils_test.cxx @@ -99,6 +99,16 @@ void test_to_int() SG_CHECK_EQUAL(strutils::to_int("-10000"), -10000); } +void test_iequals() +{ + SG_VERIFY(strutils::iequals("abcdef", "AbCDeF")); + SG_VERIFY(strutils::iequals("", "")); + SG_VERIFY(!strutils::iequals("abcdE", "ABCD")); + SG_VERIFY(strutils::iequals("%$abcdef12", "%$AbCDeF12")); + SG_VERIFY(strutils::iequals("VOR-DME", "vor-dme")); + SG_VERIFY(!strutils::iequals("VOR-DME", "vor_dme")); +} + // Auxiliary function for test_readNonNegativeInt() void aux_readNonNegativeInt_setUpOStringStream(std::ostringstream& oss, int base) { @@ -737,6 +747,7 @@ int main(int argc, char* argv[]) test_utf8Convert(); test_parseGeod(); test_formatGeod(); + test_iequals(); return EXIT_SUCCESS; } diff --git a/simgear/nasal/cppbind/NasalHash.hxx b/simgear/nasal/cppbind/NasalHash.hxx index aac672fd..59281643 100644 --- a/simgear/nasal/cppbind/NasalHash.hxx +++ b/simgear/nasal/cppbind/NasalHash.hxx @@ -24,6 +24,11 @@ #include #include +#if BOOST_VERSION >= 105600 +#include +#else +#include +#endif namespace nasal { diff --git a/simgear/props/props.cxx b/simgear/props/props.cxx index 8b599632..9d941559 100644 --- a/simgear/props/props.cxx +++ b/simgear/props/props.cxx @@ -90,6 +90,29 @@ struct PathComponent * Name: [_a-zA-Z][-._a-zA-Z0-9]* */ +namespace +{ +// Parsing property names is a profiling hotspot. The regular C +// library functions interact with the locale and are therefore quite +// heavyweight. We only support ASCII letters and numbers in property +// names, so use these simple functions instead. + +inline bool isalpha_c(int c) +{ + return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a'); +} + +inline bool isdigit_c(int c) +{ + return c <= '9' && c >= '0'; +} + +inline bool isspecial_c(int c) +{ + return c == '_' || c == '-' || c == '.'; +} +} + template inline Range parse_name (const SGPropertyNode *node, const Range &path) @@ -104,21 +127,20 @@ parse_name (const SGPropertyNode *node, const Range &path) } if (i != max && *i != '/') throw std::string("illegal character after . or .."); - } else if (isalpha(*i) || *i == '_') { + } else if (isalpha_c(*i) || *i == '_') { i++; // The rules inside a name are a little // less restrictive. while (i != max) { - if (isalpha(*i) || isdigit(*i) || *i == '_' || - *i == '-' || *i == '.') { + if (isalpha_c(*i) || isdigit_c(*i) || isspecial_c(*i)) { // name += path[i]; } else if (*i == '[' || *i == '/') { break; } else { std::string err = "'"; err.push_back(*i); - err.append("' found in propertyname after '"+node->getNameString()+"'"); + err.append("' found in propertyname after '"+node->getPath()+"'"); err.append("\nname may contain only ._- and alphanumeric characters"); throw err; } @@ -130,7 +152,7 @@ parse_name (const SGPropertyNode *node, const Range &path) if (path.begin() == i) { std::string err = "'"; err.push_back(*i); - err.append("' found in propertyname after '"+node->getNameString()+"'"); + err.append("' found in propertyname after '"+node->getPath()+"'"); err.append("\nname must begin with alpha or '_'"); throw err; } @@ -157,8 +179,8 @@ inline bool validateName(const std::string& name) } return rv; #else - return all(make_iterator_range(name.begin(), name.end()), - is_alnum() || is_any_of("_-.")); + return std::all_of(name.begin() + 1, name.end(), + [](int c){ return isalpha_c(c) || isdigit_c(c) || isspecial_c(c); }); #endif } @@ -536,7 +558,7 @@ find_node (SGPropertyNode * current, using namespace boost; typedef split_iterator::type> PathSplitIterator; - + PathSplitIterator itr = make_split_iterator(path, first_finder("/", is_equal())); if (*path.begin() == '/') @@ -874,7 +896,7 @@ SGPropertyNode::SGPropertyNode (const SGPropertyNode &node) } switch (_type) { case props::BOOL: - set_bool(node.get_bool()); + set_bool(node.get_bool()); break; case props::INT: set_int(node.get_int()); @@ -967,7 +989,7 @@ SGPropertyNode::alias (SGPropertyNode * target) for (auto p = target; p; p = ((p->_type == props::ALIAS) ? p->_value.alias : nullptr)) { if (p == this) return false; } - + clearValue(); get(target); _value.alias = target; @@ -993,7 +1015,7 @@ SGPropertyNode::alias (SGPropertyNode * target) else if (_tied) { - SG_LOG(SG_GENERAL, SG_ALERT, "alias(): " << getPath() << + SG_LOG(SG_GENERAL, SG_ALERT, "alias(): " << getPath() << " is a tied property. It cannot alias " << target->getPath() << "."); } @@ -1316,7 +1338,7 @@ SGPropertyNode::getType () const } -bool +bool SGPropertyNode::getBoolValue () const { // Shortcut for common case @@ -1349,7 +1371,7 @@ SGPropertyNode::getBoolValue () const } } -int +int SGPropertyNode::getIntValue () const { // Shortcut for common case @@ -1382,7 +1404,7 @@ SGPropertyNode::getIntValue () const } } -long +long SGPropertyNode::getLongValue () const { // Shortcut for common case @@ -1415,7 +1437,7 @@ SGPropertyNode::getLongValue () const } } -float +float SGPropertyNode::getFloatValue () const { // Shortcut for common case @@ -1448,7 +1470,7 @@ SGPropertyNode::getFloatValue () const } } -double +double SGPropertyNode::getDoubleValue () const { // Shortcut for common case @@ -2574,7 +2596,7 @@ template<> std::ostream& SGRawBase::printOn(std::ostream& stream) const { const SGVec4d vec - = static_cast*>(this)->getValue(); + = static_cast*>(this)->getValue(); for (int i = 0; i < 4; ++i) { stream << vec[i]; if (i < 3) diff --git a/simgear/props/props.hxx b/simgear/props/props.hxx index c94186cb..3afc8796 100644 --- a/simgear/props/props.hxx +++ b/simgear/props/props.hxx @@ -55,13 +55,18 @@ namespace boost { struct disable_if : public disable_if_c {}; } #else -# include # include +#if BOOST_VERSION >= 105600 +#include +#else +#include +#endif # include # include # include #endif + #include #include diff --git a/simgear/scene/CMakeLists.txt b/simgear/scene/CMakeLists.txt index ccbd9416..e9bb45a5 100644 --- a/simgear/scene/CMakeLists.txt +++ b/simgear/scene/CMakeLists.txt @@ -9,6 +9,7 @@ foreach( mylibfolder tgdb util tsync + viewer ) add_subdirectory(${mylibfolder}) diff --git a/simgear/scene/material/EffectCullVisitor.cxx b/simgear/scene/material/EffectCullVisitor.cxx index 22d1ab02..a016a455 100644 --- a/simgear/scene/material/EffectCullVisitor.cxx +++ b/simgear/scene/material/EffectCullVisitor.cxx @@ -34,8 +34,9 @@ namespace simgear using osgUtil::CullVisitor; -EffectCullVisitor::EffectCullVisitor(bool collectLights) : - _collectLights(collectLights) +EffectCullVisitor::EffectCullVisitor(bool collectLights, Effect *effectOverride) : + _collectLights(collectLights), + _effectOverride(effectOverride) { } @@ -61,12 +62,18 @@ void EffectCullVisitor::apply(osg::Geode& node) if (_collectLights && ( eg->getNodeMask() & MODELLIGHT_BIT ) ) { _lightList.push_back( eg ); } - Effect* effect = eg->getEffect(); + Effect *effect; + if (_effectOverride) { + effect = _effectOverride; + } else { + effect = eg->getEffect(); + if (!effect) { + CullVisitor::apply(node); + return; + } + } Technique* technique = 0; - if (!effect) { - CullVisitor::apply(node); - return; - } else if (!(technique = effect->chooseTechnique(&getRenderInfo()))) { + if (!(technique = effect->chooseTechnique(&getRenderInfo()))) { return; } // push the node's state. diff --git a/simgear/scene/material/EffectCullVisitor.hxx b/simgear/scene/material/EffectCullVisitor.hxx index ec8f223a..ef93baa3 100644 --- a/simgear/scene/material/EffectCullVisitor.hxx +++ b/simgear/scene/material/EffectCullVisitor.hxx @@ -29,11 +29,12 @@ class Texture2D; namespace simgear { +class Effect; class EffectGeode; class EffectCullVisitor : public osgUtil::CullVisitor { public: - EffectCullVisitor(bool collectLights = false); + EffectCullVisitor(bool collectLights = false, Effect *effectOverride = 0); EffectCullVisitor(const EffectCullVisitor&); virtual osgUtil::CullVisitor* clone() const; using osgUtil::CullVisitor::apply; @@ -48,6 +49,7 @@ private: std::map > _bufferList; std::vector > _lightList; bool _collectLights; + osg::ref_ptr _effectOverride; }; } #endif diff --git a/simgear/scene/material/TextureBuilder.cxx b/simgear/scene/material/TextureBuilder.cxx index 43343169..01315aa2 100644 --- a/simgear/scene/material/TextureBuilder.cxx +++ b/simgear/scene/material/TextureBuilder.cxx @@ -23,6 +23,7 @@ #include "Pass.hxx" +#include #include #include #include @@ -295,11 +296,9 @@ bool setAttrs(const TexTuple& attrs, Texture* tex, } else if (t < s && 32 <= t) { SGSceneFeatures::instance()->setTextureCompression(tex); } - tex->setMaxAnisotropy(SGSceneFeatures::instance() - ->getTextureFilter()); + tex->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); } else { - SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " - << imageName); + SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " << imageName); return false; } @@ -611,7 +610,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* SGReaderWriterOptions* wOpts = (SGReaderWriterOptions*)options; SGReaderWriterOptions::LoadOriginHint origLOH = wOpts->getLoadOriginHint(); wOpts->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS); -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<0>(), options); #else result = osgDB::readRefImageFile(_tuple.get<0>(), options); @@ -620,7 +619,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::POSITIVE_X, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<1>(), options); #else result = osgDB::readRefImageFile(_tuple.get<1>(), options); @@ -629,7 +628,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::NEGATIVE_X, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<2>(), options); #else result = osgDB::readRefImageFile(_tuple.get<2>(), options); @@ -638,7 +637,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::POSITIVE_Y, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<3>(), options); #else result = osgDB::readRefImageFile(_tuple.get<3>(), options); @@ -647,7 +646,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::NEGATIVE_Y, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<4>(), options); #else result = osgDB::readRefImageFile(_tuple.get<4>(), options); @@ -656,7 +655,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::POSITIVE_Z, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<5>(), options); #else result = osgDB::readRefImageFile(_tuple.get<5>(), options); @@ -685,7 +684,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* return cubeTexture.release(); osgDB::ReaderWriter::ReadResult result; -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(texname, options); #else result = osgDB::readRefImageFile(texname, options); diff --git a/simgear/scene/model/BVHPageNodeOSG.cxx b/simgear/scene/model/BVHPageNodeOSG.cxx index a2f15bcb..326585ab 100644 --- a/simgear/scene/model/BVHPageNodeOSG.cxx +++ b/simgear/scene/model/BVHPageNodeOSG.cxx @@ -24,6 +24,7 @@ #include "../../bvh/BVHPageRequest.hxx" #include "../../bvh/BVHPager.hxx" +#include #include #include #include diff --git a/simgear/scene/model/ModelRegistry.cxx b/simgear/scene/model/ModelRegistry.cxx index ca2beb1f..651a4ad7 100644 --- a/simgear/scene/model/ModelRegistry.cxx +++ b/simgear/scene/model/ModelRegistry.cxx @@ -397,7 +397,7 @@ ModelRegistry::readImage(const string& fileName, if (iter != imageCallbackMap.end() && iter->second.valid()) return iter->second->readImage(fileName, opt); string absFileName = SGModelLib::findDataFile(fileName, opt); - + string originalFileName = absFileName; if (!fileExists(absFileName)) { SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \"" << fileName << "\""); @@ -426,21 +426,29 @@ ModelRegistry::readImage(const string& fileName, boost::optional cachehash = filename_hash_cache.get(absFileName); if (cachehash) { hash = *cachehash; -// SG_LOG(SG_IO, SG_ALERT, "Hash for " + absFileName + " in cache " + hash); + // SG_LOG(SG_IO, SG_ALERT, "Hash for " + absFileName + " in cache " + hash); } else { -// SG_LOG(SG_IO, SG_ALERT, "Creating hash for " + absFileName); - hash = f.computeHash(); + // SG_LOG(SG_IO, SG_ALERT, "Creating hash for " + absFileName); + try { + hash = f.computeHash(); + } + catch (sg_io_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "Modelregistry::failed to compute filehash '" << absFileName << "' " << e.getFormattedMessage()); + hash = std::string(); + } + } + if (hash != std::string()) { filename_hash_cache.insert(absFileName, hash); boost::optional cacheFilename = filename_hash_cache.findValue(hash); // possibly a shared texture - but warn the user to allow investigation. if (cacheFilename && *cacheFilename != absFileName) { - SG_LOG(SG_IO, SG_ALERT, " Already have " + hash + " : " + *cacheFilename + " not "+absFileName); + SG_LOG(SG_IO, SG_ALERT, " Already have " + hash + " : " + *cacheFilename + " not " + absFileName); } -// SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); + // SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); } - newName = cache_root + "/" + hash.substr(0,2) + "/" + hash + ".cache.dds"; + newName = cache_root + "/" + hash.substr(0, 2) + "/" + hash + ".cache.dds"; } else { @@ -454,7 +462,7 @@ ModelRegistry::readImage(const string& fileName, // //} - if (fileExists(newName) && doRefresh) { + if (newName != std::string() && fileExists(newName) && doRefresh) { if (!filesCleaned.contains(newName)) { SG_LOG(SG_IO, SG_ALERT, "Removing previously cached effects image " + newName); SGPath(newName).remove(); @@ -463,7 +471,7 @@ ModelRegistry::readImage(const string& fileName, } - if (!fileExists(newName)) { + if (newName != std::string() && !fileExists(newName)) { res = registry->readImageImplementation(absFileName, opt); if (res.validImage()) { osg::ref_ptr srcImage = res.getImage(); @@ -637,7 +645,8 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); } } else { - absFileName = newName; + if (newName != std::string()) + absFileName = newName; } } } @@ -658,10 +667,8 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); if (srcImage1->getFileName().empty()) { srcImage1->setFileName(absFileName); } + srcImage1->setFileName(originalFileName); - if (srcImage1->getName().empty()) { - srcImage1->setName(absFileName); - } if(cache_active && getFileExtension(absFileName) != "dds") { if (processor) { diff --git a/simgear/scene/model/SGPickAnimation.cxx b/simgear/scene/model/SGPickAnimation.cxx index 6551500f..9b9f5249 100644 --- a/simgear/scene/model/SGPickAnimation.cxx +++ b/simgear/scene/model/SGPickAnimation.cxx @@ -582,20 +582,18 @@ public: } } } - - virtual bool buttonPressed( int button, - const osgGA::GUIEventAdapter& ea, - const Info& ) - { + + bool buttonPressed( int button, const osgGA::GUIEventAdapter& ea, const Info& ) override + { if (!_condition || _condition->test()) { // the 'be nice to Mac / laptop' users option; alt-clicking spins the - // opposite direction. Should make this configurable + // opposite direction. Should make this configurable if ((button == 0) && (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) { button = 1; } - int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4; - int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3; + const int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3; + const int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4; _direction = DIRECTION_NONE; if ((button == 0) || (button == increaseMouseWheel)) { diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index 64ad2754..c09a9579 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -338,7 +339,7 @@ sgLoad3DModel_internal(const SGPath& path, options->setDatabasePath(texturepath.local8BitStr()); osgDB::ReaderWriter::ReadResult modelResult; -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) modelResult = osgDB::readNodeFile(modelpath.local8BitStr(), options.get()); #else modelResult = osgDB::readRefNodeFile(modelpath.local8BitStr(), options.get()); diff --git a/simgear/scene/model/model.cxx b/simgear/scene/model/model.cxx index 737c7597..3eea102f 100644 --- a/simgear/scene/model/model.cxx +++ b/simgear/scene/model/model.cxx @@ -41,7 +41,7 @@ SGLoadTexture2D(bool staticTexture, const std::string& path, const osgDB::Options* options, bool wrapu, bool wrapv, int) { - osg::Image* image; + osg::ref_ptr image; if (options) #if OSG_VERSION_LESS_THAN(3,4,0) image = osgDB::readImageFile(path, options); @@ -57,6 +57,8 @@ SGLoadTexture2D(bool staticTexture, const std::string& path, osg::ref_ptr texture = new osg::Texture2D; texture->setImage(image); + texture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); + if (staticTexture) texture->setDataVariance(osg::Object::STATIC); if (wrapu) @@ -137,34 +139,45 @@ Texture2D* TextureUpdateVisitor::textureReplace(int unit, const StateAttribute* if (image) { // The currently loaded file name fullFilePath = &image->getFileName(); - } else { fullFilePath = &texture->getName(); } + // The short name string fileName = getSimpleFileName(*fullFilePath); if (fileName.empty()) return 0; + // The name that should be found with the current database path string fullLiveryFile = findFileInPath(fileName, _pathList); // If it is empty or they are identical then there is nothing to do if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath) return 0; + #if OSG_VERSION_LESS_THAN(3,4,0) Image* newImage = readImageFile(fullLiveryFile); #else - Image* newImage = readRefImageFile(fullLiveryFile); + osg::ref_ptr newImage = readRefImageFile(fullLiveryFile); #endif if (!newImage) return 0; + CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES); Texture2D* newTexture = static_cast(copyOp(texture)); - if (!newTexture) { + if (!newTexture) return 0; - } else { - newTexture->setImage(newImage); - return newTexture; + + newTexture->setImage(newImage); +#if OSG_VERSION_LESS_THAN(3,4,0) + if (newImage->valid()) +#else + if (newImage.valid()) +#endif + { + newTexture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); } + + return newTexture; } StateSet* TextureUpdateVisitor::cloneStateSet(const StateSet* stateSet) diff --git a/simgear/scene/model/modellib.cxx b/simgear/scene/model/modellib.cxx index d11e0d21..92b19196 100644 --- a/simgear/scene/model/modellib.cxx +++ b/simgear/scene/model/modellib.cxx @@ -21,6 +21,7 @@ #include +#include #include #include #include diff --git a/simgear/scene/sky/sky.cxx b/simgear/scene/sky/sky.cxx index 389d8bf4..25df35d7 100644 --- a/simgear/scene/sky/sky.cxx +++ b/simgear/scene/sky/sky.cxx @@ -94,7 +94,7 @@ void SGSky::build( double h_radius_m, planets = new SGStars; _ephTransform->addChild( planets->build(eph.getNumPlanets(), eph.getPlanets(), h_radius_m) ); - stars = new SGStars; + stars = new SGStars(property_tree_node); _ephTransform->addChild( stars->build(eph.getNumStars(), eph.getStars(), h_radius_m) ); moon = new SGMoon; diff --git a/simgear/scene/sky/stars.cxx b/simgear/scene/sky/stars.cxx index d4611e01..a079cdee 100644 --- a/simgear/scene/sky/stars.cxx +++ b/simgear/scene/sky/stars.cxx @@ -30,9 +30,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -47,9 +49,14 @@ #include "stars.hxx" // Constructor -SGStars::SGStars( void ) : -old_phase(-1) +SGStars::SGStars( SGPropertyNode* props ) : + old_phase(-1) { + if (props) { + // don't create here - if it's not defined, we won't use the cutoff + // from a property + _cutoffProperty = props->getNode("star-magnitude-cutoff"); + } } @@ -117,69 +124,102 @@ SGStars::build( int num, const SGVec3d star_data[], double star_dist ) { // 0 degrees = high noon // 90 degrees = sun rise/set // 180 degrees = darkest midnight -bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) { - // cout << "repainting stars" << endl; - // double min = 100; - // double max = -100; + +bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) +{ double mag, nmag, alpha, factor, cutoff; + /* + maximal magnitudes under dark sky on Earth, from Eq.(90) and (91) of astro-ph/1405.4209 + For (18 < musky < 20) + mmax = 0.27 musky + 0.8 - 2.5 * log(F) + + For (19.5 µsky 22) + mmax = 0.383 musky - 1.44 - 2.5 * log(F) + + + Let's take F = 1.4 for healthy young pilot + mudarksky ~ 22 mag/arcsec^2 => mmax=6.2 + muastrotwilight ~ 20 mag/arsec^2 => mmax=5.4 + mu99deg ~ 17.5 mag/arcsec^2 => mmax=4.7 + mu97.5deg ~ 16 mag/arcsec^2 => ? let's keep it rough + */ + + double mag_nakedeye = 6.2; + double mag_twilight_astro = 5.4; + double mag_twilight_nautic = 4.7; + + // sirius, brightest star (not brightest object) + double mag_min = -1.46; + int phase; // determine which star structure to draw - if ( sun_angle > (SGD_PI_2 + 10.0 * SGD_DEGREES_TO_RADIANS ) ) { - // deep night + if ( sun_angle > (SGD_PI_2 + 18.0 * SGD_DEGREES_TO_RADIANS ) ) { + // deep night, atmosphere is not lighten by the sun factor = 1.0; - cutoff = 4.5; + cutoff = mag_nakedeye; phase = 0; - } else if ( sun_angle > (SGD_PI_2 + 8.8 * SGD_DEGREES_TO_RADIANS ) ) { + } else if ( sun_angle > (SGD_PI_2 + 12.0 * SGD_DEGREES_TO_RADIANS ) ) { + // less than 18deg and more than 12deg is astronomical twilight factor = 1.0; - cutoff = 3.8; + cutoff = mag_twilight_astro; phase = 1; + } else if ( sun_angle > (SGD_PI_2 + 9.0 * SGD_DEGREES_TO_RADIANS ) ) { + // less 12deg and more than 6deg is is nautical twilight + factor = 1.0; + cutoff = mag_twilight_nautic; + phase = 2; } else if ( sun_angle > (SGD_PI_2 + 7.5 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.95; cutoff = 3.1; - phase = 2; + phase = 3; } else if ( sun_angle > (SGD_PI_2 + 7.0 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.9; cutoff = 2.4; - phase = 3; + phase = 4; } else if ( sun_angle > (SGD_PI_2 + 6.5 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.85; cutoff = 1.8; - phase = 4; + phase = 5; } else if ( sun_angle > (SGD_PI_2 + 6.0 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.8; cutoff = 1.2; - phase = 5; + phase = 6; } else if ( sun_angle > (SGD_PI_2 + 5.5 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.75; cutoff = 0.6; - phase = 6; + phase = 7; } else { // early dusk or late dawn factor = 0.7; cutoff = 0.0; - phase = 7; + phase = 8; } - - if( phase != old_phase ) { + + if (_cutoffProperty) { + double propCutoff = _cutoffProperty->getDoubleValue(); + cutoff = std::min(propCutoff, cutoff); + } + + if ((phase != old_phase) || (cutoff != _cachedCutoff)) { // cout << " phase change, repainting stars, num = " << num << endl; old_phase = phase; + _cachedCutoff = cutoff; + for ( int i = 0; i < num; ++i ) { // if ( star_data[i][2] < min ) { min = star_data[i][2]; } // if ( star_data[i][2] > max ) { max = star_data[i][2]; } - // magnitude ranges from -1 (bright) to 4 (dim). The + // magnitude ranges from -1 (bright) to 6 (dim). The // range of star and planet magnitudes can actually go // outside of this, but for our purpose, if it is brighter - // that -1, we'll color it full white/alpha anyway and 4 - // is a convenient cutoff point which keeps the number of - // stars drawn at about 500. + // that magmin, we'll color it full white/alpha anyway // color (magnitude) mag = star_data[i][2]; if ( mag < cutoff ) { - nmag = ( 4.5 - mag ) / 5.5; // translate to 0 ... 1.0 scale + nmag = ( cutoff - mag ) / (cutoff - mag_min); // translate to 0 ... 1.0 scale alpha = nmag * 0.85 + 0.15; // translate to a 0.15 ... 1.0 scale alpha *= factor; // dim when the sun is brighter } else { @@ -190,11 +230,8 @@ bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) { if (alpha < 0.0) { alpha = 0.0; } (*cl)[i] = osg::Vec4(1, 1, 1, alpha); - // cout << "alpha[" << i << "] = " << alpha << endl; } cl->dirty(); - } else { - // cout << " no phase change, skipping" << endl; } // cout << "min = " << min << " max = " << max << " count = " << num diff --git a/simgear/scene/sky/stars.hxx b/simgear/scene/sky/stars.hxx index bf311e31..10b0140c 100644 --- a/simgear/scene/sky/stars.hxx +++ b/simgear/scene/sky/stars.hxx @@ -33,18 +33,20 @@ #include #include - +#include class SGStars : public SGReferenced { osg::ref_ptr cl; int old_phase; // data for optimization - + + double _cachedCutoff = 0.0; + SGPropertyNode_ptr _cutoffProperty; public: // Constructor - SGStars( void ); + SGStars( SGPropertyNode* props = nullptr); // Destructor ~SGStars( void ); diff --git a/simgear/scene/tgdb/ReaderWriterSPT.cxx b/simgear/scene/tgdb/ReaderWriterSPT.cxx index 66fad24b..638ce476 100644 --- a/simgear/scene/tgdb/ReaderWriterSPT.cxx +++ b/simgear/scene/tgdb/ReaderWriterSPT.cxx @@ -25,6 +25,7 @@ #include +#include #include #include #include diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index 4a3887a4..a417df45 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -620,9 +620,11 @@ struct ReaderWriterSTG::_ModelBin { pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback"); // Objects may end up displayed up to 2x the object range. - pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough + SG_TILE_RADIUS); + pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); + pagedLOD->setRadius(SG_TILE_RADIUS); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); - SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough + SG_TILE_RADIUS)); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough)); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Radius: " << SG_TILE_RADIUS); return pagedLOD; } } diff --git a/simgear/scene/tsync/terrasync.cxx b/simgear/scene/tsync/terrasync.cxx index 01385b0a..69e381f2 100644 --- a/simgear/scene/tsync/terrasync.cxx +++ b/simgear/scene/tsync/terrasync.cxx @@ -773,7 +773,7 @@ void SGTerraSync::WorkerThread::initCompletedTilesPersistentCache() try { readProperties(_persistentCachePath, cacheRoot); } catch (sg_exception& e) { - SG_LOG(SG_TERRASYNC, SG_INFO, "corrupted persistent cache, discarding"); + SG_LOG(SG_TERRASYNC, SG_INFO, "corrupted persistent cache, discarding " << e.getFormattedMessage()); return; } @@ -864,28 +864,31 @@ void SGTerraSync::init() _inited = true; assert(_terraRoot); - _terraRoot->setBoolValue("built-in-svn-available", true); reinit(); } void SGTerraSync::shutdown() { + SG_LOG(SG_TERRASYNC, SG_INFO, "Shutdown"); _workerThread->stop(); } void SGTerraSync::reinit() { + auto enabled = _enabledNode->getBoolValue(); // do not reinit when enabled and we're already up and running - if ((_terraRoot->getBoolValue("enabled",false)) && _workerThread->isRunning()) + if (enabled && _workerThread->isRunning()) { + _availableNode->setBoolValue(true); return; } - + _stalledNode->setBoolValue(false); _workerThread->stop(); - if (_terraRoot->getBoolValue("enabled",false)) + if (enabled) { + _availableNode->setBoolValue(true); _workerThread->setHTTPServer( _terraRoot->getStringValue("http-server","automatic") ); _workerThread->setSceneryVersion( _terraRoot->getStringValue("scenery-version","ws20") ); _workerThread->setProtocol( _terraRoot->getStringValue("protocol","") ); @@ -907,6 +910,8 @@ void SGTerraSync::reinit() syncAirportsModels(); } } + else + _availableNode->setBoolValue(false); _stalledNode->setBoolValue(_workerThread->isStalled()); } @@ -919,18 +924,33 @@ void SGTerraSync::bind() _bound = true; - _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); - _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + //_terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + //_terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + _terraRoot->getNode("intialized", true)->setBoolValue(true); // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups) _stalledNode = _terraRoot->getNode("stalled", true); _stalledNode->setBoolValue(_workerThread->isStalled()); - _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true); +// _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true); + + _activeNode = _terraRoot->getNode("active", true); + + _busyNode = _terraRoot->getNode("busy", true); + _updateCountNode = _terraRoot->getNode("update-count", true); + _errorCountNode = _terraRoot->getNode("error-count", true); + _tileCountNode = _terraRoot->getNode("tile-count", true); + _cacheHitsNode = _terraRoot->getNode("cache-hits", true); + _transferRateBytesSecNode = _terraRoot->getNode("transfer-rate-bytes-sec", true); + _pendingKbytesNode = _terraRoot->getNode("pending-kbytes", true); + _downloadedKBtesNode = _terraRoot->getNode("downloaded-kbytes", true); + _enabledNode = _terraRoot->getNode("enabled", true); + _availableNode = _terraRoot->getNode("available", true); + //_busyNode->setAttribute(SGPropertyNode::WRITE, false); + //_activeNode->setAttribute(SGPropertyNode::WRITE, false); + //_updateCountNode->setAttribute(SGPropertyNode::WRITE, false); + //_errorCountNode->setAttribute(SGPropertyNode::WRITE, false); + //_tileCountNode->setAttribute(SGPropertyNode::WRITE, false); + } void SGTerraSync::unbind() @@ -947,19 +967,33 @@ void SGTerraSync::unbind() void SGTerraSync::update(double) { + auto enabled = _enabledNode->getBoolValue(); + auto worker_running = _workerThread->isRunning(); + + // see if the enabled status has changed; and if so take the appropriate action. + if (enabled && !worker_running) + { + reinit(); + SG_LOG(SG_TERRASYNC, SG_ALERT, "Terrasync started"); + } + else if (!enabled && worker_running) + { + reinit(); + SG_LOG(SG_TERRASYNC, SG_ALERT, "Terrasync stopped"); + } TerrasyncThreadState copiedState(_workerThread->threadsafeCopyState()); - _terraRoot->setBoolValue("busy", copiedState._busy); - _terraRoot->setIntValue("update-count", copiedState._success_count); - _terraRoot->setIntValue("error-count", copiedState._fail_count); - _terraRoot->setIntValue("tile-count", copiedState._updated_tile_count); - _terraRoot->setIntValue("cache-hits", copiedState._cache_hits); - _terraRoot->setIntValue("transfer-rate-bytes-sec", copiedState._transfer_rate); - _terraRoot->setIntValue("downloaded-kbytes", copiedState._total_kb_downloaded); - _terraRoot->setIntValue("pending-kbytes", copiedState._totalKbPending); + _busyNode->setIntValue(copiedState._busy); + _updateCountNode->setIntValue(copiedState._success_count); + _errorCountNode->setIntValue(copiedState._fail_count); + _tileCountNode->setIntValue(copiedState._updated_tile_count); + _cacheHitsNode->setIntValue(copiedState._cache_hits); + _transferRateBytesSecNode->setIntValue(copiedState._transfer_rate); + _pendingKbytesNode->setIntValue(copiedState._totalKbPending); + _downloadedKBtesNode->setIntValue(copiedState._total_kb_downloaded); _stalledNode->setBoolValue(_workerThread->isStalled()); - _terraRoot->setBoolValue("active", _workerThread->isRunning()); + _activeNode->setBoolValue(worker_running); while (_workerThread->hasNewTiles()) { diff --git a/simgear/scene/tsync/terrasync.hxx b/simgear/scene/tsync/terrasync.hxx index 78a3bd62..4e806568 100644 --- a/simgear/scene/tsync/terrasync.hxx +++ b/simgear/scene/tsync/terrasync.hxx @@ -92,6 +92,17 @@ private: SGPropertyNode_ptr _renderingRoot; SGPropertyNode_ptr _stalledNode; SGPropertyNode_ptr _cacheHits; + SGPropertyNode_ptr _busyNode; + SGPropertyNode_ptr _activeNode; + SGPropertyNode_ptr _enabledNode; + SGPropertyNode_ptr _availableNode; + SGPropertyNode_ptr _updateCountNode; + SGPropertyNode_ptr _errorCountNode; + SGPropertyNode_ptr _tileCountNode; + SGPropertyNode_ptr _cacheHitsNode; + SGPropertyNode_ptr _transferRateBytesSecNode; + SGPropertyNode_ptr _pendingKbytesNode; + SGPropertyNode_ptr _downloadedKBtesNode; // we manually bind+init TerraSync during early startup // to get better overlap of slow operations (Shared Models sync diff --git a/simgear/scene/viewer/CMakeLists.txt b/simgear/scene/viewer/CMakeLists.txt new file mode 100644 index 00000000..d26297fc --- /dev/null +++ b/simgear/scene/viewer/CMakeLists.txt @@ -0,0 +1,17 @@ +set(HEADERS + ClusteredForward.hxx + Compositor.hxx + CompositorBuffer.hxx + CompositorPass.hxx + CompositorUtil.hxx + ) + +set(SOURCES + ClusteredForward.cxx + Compositor.cxx + CompositorBuffer.cxx + CompositorPass.cxx + CompositorUtil.cxx + ) + +simgear_scene_component(viewer scene/viewer "${SOURCES}" "${HEADERS}") diff --git a/simgear/scene/viewer/ClusteredForward.cxx b/simgear/scene/viewer/ClusteredForward.cxx new file mode 100644 index 00000000..6e5f4098 --- /dev/null +++ b/simgear/scene/viewer/ClusteredForward.cxx @@ -0,0 +1,216 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "ClusteredForward.hxx" + +#include +#include +#include +#include +#include +#include + +namespace simgear { +namespace compositor { + +///// BEGIN DEBUG +#define DATA_SIZE 24 +const GLfloat LIGHT_DATA[DATA_SIZE] = { + 0.0, 0.0, -10.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 10.0, 1.0, 0.0, 1.0, 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0 +}; + +#define MAX_LIGHT_INDICES 4096 +#define MAX_POINT_LIGHTS 256 + +struct Light { + osg::Vec3 position; + float range; +}; + +#define NUM_LIGHTS 2 +Light LIGHT_LIST[NUM_LIGHTS] = { + {osg::Vec3(0.0f, 0.0f, -10.0f), 10.0f}, + {osg::Vec3(0.0f, 0.0f, 5.0f), 1000.0f} +}; +///// END DEBUG + +ClusteredForwardDrawCallback::ClusteredForwardDrawCallback() : + _initialized(false), + _tile_size(64), + _light_grid(new osg::Image), + _light_indices(new osg::Image), + _light_data(new osg::FloatArray(MAX_POINT_LIGHTS)) +{ +} + +void +ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const +{ + osg::Camera *camera = renderInfo.getCurrentCamera(); + const osg::Viewport *vp = camera->getViewport(); + const int width = vp->width(); + const int height = vp->height(); + + // Round up + int n_htiles = (width + _tile_size - 1) / _tile_size; + int n_vtiles = (height + _tile_size - 1) / _tile_size; + + if (!_initialized) { + // Create and associate the light grid 3D texture + _light_grid->allocateImage(n_htiles, n_vtiles, 1, + GL_RGB_INTEGER_EXT, GL_UNSIGNED_SHORT); + _light_grid->setInternalTextureFormat(GL_RGB16UI_EXT); + + osg::ref_ptr light_grid_tex = new osg::Texture3D; + light_grid_tex->setResizeNonPowerOfTwoHint(false); + light_grid_tex->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setWrap(osg::Texture3D::WRAP_S, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setWrap(osg::Texture3D::WRAP_T, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::NEAREST); + light_grid_tex->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::NEAREST); + light_grid_tex->setImage(0, _light_grid.get()); + + camera->getOrCreateStateSet()->setTextureAttributeAndModes( + 10, light_grid_tex.get(), osg::StateAttribute::ON); + + // Create and associate the light indices TBO + _light_indices->allocateImage(4096, 1, 1, GL_RED_INTEGER_EXT, GL_UNSIGNED_SHORT); + + osg::ref_ptr light_indices_tbo = + new osg::TextureBuffer; + light_indices_tbo->setInternalFormat(GL_R16UI); + light_indices_tbo->setImage(_light_indices.get()); + + camera->getOrCreateStateSet()->setTextureAttribute( + 11, light_indices_tbo.get()); + + // Create and associate the light data UBO + osg::ref_ptr light_data_ubo = + new osg::UniformBufferObject; + _light_data->setBufferObject(light_data_ubo.get()); + +#if OSG_VERSION_LESS_THAN(3,6,0) + osg::ref_ptr light_data_ubb = + new osg::UniformBufferBinding(0, light_data_ubo.get(), + 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); +#else + osg::ref_ptr light_data_ubb = + new osg::UniformBufferBinding(0, _light_data.get(), + 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); +#endif +light_data_ubb->setDataVariance(osg::Object::DYNAMIC); + + camera->getOrCreateStateSet()->setAttribute( + light_data_ubb.get(), osg::StateAttribute::ON); + + _initialized = true; + } + + std::vector subfrustums; + const osg::Matrix &view_matrix = camera->getViewMatrix(); + const osg::Matrix &proj_matrix = camera->getProjectionMatrix(); + osg::Matrix view_proj_inverse = osg::Matrix::inverse(view_matrix * proj_matrix); + + double x_step = (_tile_size / width) * 2.0; + double y_step = (_tile_size / height) * 2.0; + for (int y = 0; y < n_vtiles; ++y) { + for (int x = 0; x < n_htiles; ++x) { + // Create the subfrustum in clip space + double x_min = -1.0 + x_step * x; double x_max = x_min + x_step; + double y_min = -1.0 + y_step * y; double y_max = y_min + y_step; + double z_min = 1.0; double z_max = -1.0; + osg::BoundingBox subfrustum_bb( + x_min, y_min, z_min, x_max, y_max, z_max); + osg::Polytope subfrustum; + subfrustum.setToBoundingBox(subfrustum_bb); + + // Transform it to world space + subfrustum.transformProvidingInverse(view_proj_inverse); + + subfrustums.push_back(subfrustum); + } + } + + GLushort *grid_data = reinterpret_cast + (_light_grid->data()); + GLushort *index_data = reinterpret_cast + (_light_indices->data()); + + GLushort global_light_count = 0; + for (size_t i = 0; i < subfrustums.size(); ++i) { + GLushort start_offset = global_light_count; + GLushort local_light_count = 0; + + for (GLushort light_list_index = 0; + light_list_index < NUM_LIGHTS; + ++light_list_index) { + const Light &light = LIGHT_LIST[light_list_index]; + osg::BoundingSphere bs(light.position, light.range); + + if (subfrustums[i].contains(bs)) { + index_data[global_light_count] = light_list_index; + ++local_light_count; + ++global_light_count; + } + } + grid_data[i * 3 + 0] = start_offset; + grid_data[i * 3 + 1] = local_light_count; + grid_data[i * 3 + 2] = 0; + } + + _light_grid->dirty(); + _light_indices->dirty(); + + // Upload light data + for (int i = 0; i < DATA_SIZE; ++i) { + (*_light_data)[i] = LIGHT_DATA[i]; + } + + // DEBUG + /* + if (!_debug) { + for (int y = 0; y < num_vtiles; ++y) { + for (int x = 0; x < num_htiles; ++x) { + std::cout << grid_data[(y * num_htiles + x) * 3 + 0] << "," + << grid_data[(y * num_htiles + x) * 3 + 1] << " "; + } + std::cout << std::endl; + } + std::cout << "\n\n"; + + for (int i = 0; i < num_vtiles * num_htiles; ++i) { + std::cout << index_data[i] << " "; + } + std::cout << "\n"; + _debug = true; + } + */ +/* + for (int y = 0; y < num_vtiles; ++y) { + for (int x = 0; x < num_htiles; ++x) { + data[(y * num_htiles + x) * 3 + 0] = (unsigned short)x; + data[(y * num_htiles + x) * 3 + 1] = (unsigned short)y; + data[(y * num_htiles + x) * 3 + 2] = 0; + } + } + _light_grid->dirty(); +*/ +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/ClusteredForward.hxx b/simgear/scene/viewer/ClusteredForward.hxx new file mode 100644 index 00000000..8afa47b0 --- /dev/null +++ b/simgear/scene/viewer/ClusteredForward.hxx @@ -0,0 +1,40 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CLUSTERED_FORWARD_HXX +#define SG_CLUSTERED_FORWARD_HXX + +#include + +namespace simgear { +namespace compositor { + +class ClusteredForwardDrawCallback : public osg::Camera::DrawCallback { +public: + ClusteredForwardDrawCallback(); + virtual void operator()(osg::RenderInfo &renderInfo) const; +protected: + mutable bool _initialized; + int _tile_size; + osg::ref_ptr _light_grid; + osg::ref_ptr _light_indices; + osg::ref_ptr _light_data; +}; + +} // namespace compositor +} // namespace simgear + +#endif /* SG_CLUSTERED_FORWARD_HXX */ diff --git a/simgear/scene/viewer/Compositor.cxx b/simgear/scene/viewer/Compositor.cxx new file mode 100644 index 00000000..f1caf50c --- /dev/null +++ b/simgear/scene/viewer/Compositor.cxx @@ -0,0 +1,312 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "Compositor.hxx" + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "CompositorUtil.hxx" + +namespace simgear { +namespace compositor { + +Compositor * +Compositor::create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const SGPropertyNode *property_list) +{ + osg::ref_ptr compositor = new Compositor(view, gc, viewport); + compositor->_name = property_list->getStringValue("name"); + + // Read all buffers first so passes can use them + PropertyList p_buffers = property_list->getChildren("buffer"); + for (auto const &p_buffer : p_buffers) { + if (!checkConditional(p_buffer)) + continue; + const std::string &buffer_name = p_buffer->getStringValue("name"); + if (buffer_name.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Buffer requires " + "a name to be available to passes. Skipping..."); + continue; + } + Buffer *buffer = buildBuffer(compositor.get(), p_buffer); + if (buffer) + compositor->addBuffer(buffer_name, buffer); + } + // Read passes + PropertyList p_passes = property_list->getChildren("pass"); + for (auto const &p_pass : p_passes) { + if (!checkConditional(p_pass)) + continue; + Pass *pass = buildPass(compositor.get(), p_pass); + if (pass) + compositor->addPass(pass); + } + + return compositor.release(); +} + +Compositor * +Compositor::create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const std::string &name) +{ + std::string filename(name); + filename += ".xml"; + std::string abs_filename = SGModelLib::findDataFile(filename); + if (abs_filename.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Could not find file '" + << filename << "'"); + return 0; + } + + SGPropertyNode_ptr property_list = new SGPropertyNode; + try { + readProperties(abs_filename, property_list.ptr(), 0, true); + } catch (sg_io_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Failed to parse file '" + << abs_filename << "'. " << e.getFormattedMessage()); + return 0; + } + + return create(view, gc, viewport, property_list); +} + +Compositor::Compositor(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport) : + _view(view), + _gc(gc), + _viewport(viewport), + _uniforms{ + new osg::Uniform("fg_ViewportSize", osg::Vec2f()), + new osg::Uniform("fg_ViewMatrix", osg::Matrixf()), + new osg::Uniform("fg_ViewMatrixInverse", osg::Matrixf()), + new osg::Uniform("fg_ProjectionMatrix", osg::Matrixf()), + new osg::Uniform("fg_ProjectionMatrixInverse", osg::Matrixf()), + new osg::Uniform("fg_CameraPositionCart", osg::Vec3f()), + new osg::Uniform("fg_CameraPositionGeod", osg::Vec3f()) + } +{ +} + +Compositor::~Compositor() +{ +} + +void +Compositor::update(const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) +{ + for (auto &pass : _passes) { + if (pass->inherit_cull_mask) { + osg::Camera *camera = pass->camera; + osg::Camera *view_camera = _view->getCamera(); + camera->setCullMask(pass->cull_mask + & view_camera->getCullMask()); + camera->setCullMaskLeft(pass->cull_mask + & view_camera->getCullMaskLeft()); + camera->setCullMaskRight(pass->cull_mask + & view_camera->getCullMaskRight()); + } + + if (pass->update_callback.valid()) + pass->update_callback->updatePass(*pass.get(), view_matrix, proj_matrix); + } + + // Update uniforms + osg::Matrixd view_inverse = osg::Matrix::inverse(view_matrix); + osg::Vec4d camera_pos = osg::Vec4(0.0, 0.0, 0.0, 1.0) * view_inverse; + SGGeod camera_pos_geod = SGGeod::fromCart( + SGVec3d(camera_pos.x(), camera_pos.y(), camera_pos.z())); + + for (int i = 0; i < TOTAL_BUILTIN_UNIFORMS; ++i) { + osg::ref_ptr u = _uniforms[i]; + switch (i) { + case VIEWPORT_SIZE: + u->set(osg::Vec2f(_viewport->width(), _viewport->height())); + break; + case VIEW_MATRIX: + u->set(view_matrix); + break; + case VIEW_MATRIX_INV: + u->set(view_inverse); + break; + case PROJECTION_MATRIX: + u->set(proj_matrix); + break; + case PROJECTION_MATRIX_INV: + u->set(osg::Matrix::inverse(proj_matrix)); + break; + case CAMERA_POSITION_CART: + u->set(osg::Vec3f(camera_pos.x(), camera_pos.y(), camera_pos.z())); + break; + case CAMERA_POSITION_GEOD: + u->set(osg::Vec3f(camera_pos_geod.getLongitudeRad(), + camera_pos_geod.getLatitudeRad(), + camera_pos_geod.getElevationM())); + break; + default: + // Unknown uniform + break; + } + } +} + +void +Compositor::resized() +{ + // Cameras attached directly to the framebuffer were already resized by + // osg::GraphicsContext::resizedImplementation(). However, RTT cameras were + // ignored. Here we resize RTT cameras that need to match the physical + // viewport size. + for (const auto &pass : _passes) { + osg::Camera *camera = pass->camera; + if (!camera->isRenderToTextureCamera() || + pass->viewport_width_scale == 0.0f || + pass->viewport_height_scale == 0.0f) + continue; + + // Resize both the viewport and its texture attachments + camera->resize(pass->viewport_width_scale * _viewport->width(), + pass->viewport_height_scale * _viewport->height()); + } +} + +bool +Compositor::computeIntersection( + const osg::Vec2d& windowPos, + osgUtil::LineSegmentIntersector::Intersections& intersections) +{ + using osgUtil::Intersector; + using osgUtil::LineSegmentIntersector; + + osg::Camera *camera = getPass(0)->camera; + const osg::Viewport* viewport = camera->getViewport(); + SGRect viewportRect(viewport->x(), viewport->y(), + viewport->x() + viewport->width() - 1.0, + viewport->y() + viewport->height()- 1.0); + + double epsilon = 0.5; + if (!viewportRect.contains(windowPos.x(), windowPos.y(), epsilon)) + return false; + + osg::Vec4d start(windowPos.x(), windowPos.y(), 0.0, 1.0); + osg::Vec4d end(windowPos.x(), windowPos.y(), 1.0, 1.0); + osg::Matrix windowMat = viewport->computeWindowMatrix(); + osg::Matrix startPtMat = osg::Matrix::inverse(camera->getProjectionMatrix() + * windowMat); + osg::Matrix endPtMat = startPtMat; // no far camera + + start = start * startPtMat; + start /= start.w(); + end = end * endPtMat; + end /= end.w(); + osg::ref_ptr picker + = new LineSegmentIntersector(Intersector::VIEW, + osg::Vec3d(start.x(), start.y(), start.z()), + osg::Vec3d(end.x(), end.y(), end.z())); + osgUtil::IntersectionVisitor iv(picker.get()); + iv.setTraversalMask( simgear::PICK_BIT ); + + const_cast(camera)->accept(iv); + if (picker->containsIntersections()) { + intersections = picker->getIntersections(); + return true; + } + + return false; +} + +void +Compositor::addBuffer(const std::string &name, Buffer *buffer) +{ + _buffers[name] = buffer; +} + +void +Compositor::addPass(Pass *pass) +{ + if (!_view) { + SG_LOG(SG_GENERAL, SG_ALERT, "Compositor::addPass: Couldn't add camera " + "as a slave to the view. View doesn't exist!"); + return; + } + + _view->addSlave(pass->camera, pass->useMastersSceneData); + + // Install the Effect cull visitor + osgViewer::Renderer* renderer + = static_cast(pass->camera->getRenderer()); + for (int i = 0; i < 2; ++i) { + osgUtil::SceneView* sceneView = renderer->getSceneView(i); + + osg::ref_ptr identifier; + identifier = sceneView->getCullVisitor()->getIdentifier(); + + sceneView->setCullVisitor( + new EffectCullVisitor(false, pass->effect_override)); + sceneView->getCullVisitor()->setIdentifier(identifier.get()); + + identifier = sceneView->getCullVisitorLeft()->getIdentifier(); + sceneView->setCullVisitorLeft(sceneView->getCullVisitor()->clone()); + sceneView->getCullVisitorLeft()->setIdentifier(identifier.get()); + + identifier = sceneView->getCullVisitorRight()->getIdentifier(); + sceneView->setCullVisitorRight(sceneView->getCullVisitor()->clone()); + sceneView->getCullVisitorRight()->setIdentifier(identifier.get()); + } + + _passes.push_back(pass); +} + +Buffer * +Compositor::getBuffer(const std::string &name) const +{ + auto it = _buffers.find(name); + if (it == _buffers.end()) + return 0; + return it->second.get(); +} + +Pass * +Compositor::getPass(const std::string &name) const +{ + auto it = std::find_if(_passes.begin(), _passes.end(), + [&name](const osg::ref_ptr &p) { + return p->name == name; + }); + if (it == _passes.end()) + return 0; + return (*it); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/Compositor.hxx b/simgear/scene/viewer/Compositor.hxx new file mode 100644 index 00000000..c1761ef1 --- /dev/null +++ b/simgear/scene/viewer/Compositor.hxx @@ -0,0 +1,138 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_HXX +#define SG_COMPOSITOR_HXX + +#include +#include + +// For osgUtil::LineSegmentIntersector::Intersections, which is a typedef. +#include + +#include "CompositorBuffer.hxx" +#include "CompositorPass.hxx" + +class SGPropertyNode; + +namespace simgear { +namespace compositor { + +/** + * A Compositor manages the rendering pipeline of a single physical camera, + * usually via a property tree interface. + * + * The building blocks that define a Compositor are: + * - Buffers. They represent a zone of GPU memory. This is implemented in the + * form of an OpenGL texture, but any type of information can be stored + * (which can be useful in compute shaders for example). + * - Passes. They represent render operations. They can get buffers as input + * and they can output to other buffers. They are also integrated with the + * Effects framework, so the OpenGL internal state is configurable per pass. + */ +class Compositor : public osg::Referenced { +public: + enum BuiltinUniform { + VIEWPORT_SIZE = 0, + VIEW_MATRIX, + VIEW_MATRIX_INV, + PROJECTION_MATRIX, + PROJECTION_MATRIX_INV, + CAMERA_POSITION_CART, + CAMERA_POSITION_GEOD, + TOTAL_BUILTIN_UNIFORMS + }; + + Compositor(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport); + ~Compositor(); + + /** + * \brief Create a Compositor from a property tree. + * + * @param view The View where the passes will be added as slaves. + * @param gc The context where the internal osg::Cameras will draw on. + * @param viewport The viewport position and size inside the window. + * @param property_list A valid property list that describes the Compositor. + * @return A Compositor or a null pointer if there was an error. + */ + static Compositor *create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const SGPropertyNode *property_list); + /** + * \overload + * \brief Create a Compositor from a file. + * + * @param name Name of the compositor. The function will search for a file + * named .xml in $FG_ROOT. + */ + static Compositor *create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const std::string &name); + + void update(const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix); + + void resized(); + + bool computeIntersection( + const osg::Vec2d& windowPos, + osgUtil::LineSegmentIntersector::Intersections& intersections); + + const osg::GraphicsContext *getGraphicsContext() const { return _gc; } + + const osg::Viewport *getViewport() const { return _viewport; } + + typedef std::array< + osg::ref_ptr, + TOTAL_BUILTIN_UNIFORMS> BuiltinUniforms; + const BuiltinUniforms &getUniforms() const { return _uniforms; } + + void addBuffer(const std::string &name, Buffer *buffer); + void addPass(Pass *pass); + + void setName(const std::string &name) { _name = name; } + const std::string &getName() const { return _name; } + + typedef std::unordered_map> BufferMap; + const BufferMap & getBufferMap() const { return _buffers; } + Buffer * getBuffer(const std::string &name) const; + + typedef std::vector> PassList; + const PassList & getPassList() const { return _passes; } + unsigned int getNumPasses() const { return _passes.size(); } + Pass * getPass(size_t index) const { return _passes[index]; } + Pass * getPass(const std::string &name) const; + +protected: + friend class PassBuilder; + + osg::View *_view; + osg::GraphicsContext *_gc; + osg::ref_ptr _viewport; + std::string _name; + BufferMap _buffers; + PassList _passes; + BuiltinUniforms _uniforms; +}; + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_HXX */ diff --git a/simgear/scene/viewer/CompositorBuffer.cxx b/simgear/scene/viewer/CompositorBuffer.cxx new file mode 100644 index 00000000..2acb6be9 --- /dev/null +++ b/simgear/scene/viewer/CompositorBuffer.cxx @@ -0,0 +1,228 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "CompositorBuffer.hxx" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Compositor.hxx" +#include "CompositorUtil.hxx" + +namespace simgear { +namespace compositor { + +struct BufferFormat { + GLint internal_format; + GLenum source_format; + GLenum source_type; +}; + +PropStringMap buffer_format_map { + {"rgb8", {GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE}}, + {"rgba8", {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}}, + {"rgb16f", {GL_RGB16F_ARB, GL_RGBA, GL_FLOAT}}, + {"rgb32f", {GL_RGB32F_ARB, GL_RGBA, GL_FLOAT}}, + {"rgba16f", {GL_RGBA16F_ARB, GL_RGBA, GL_FLOAT}}, + {"rgba32f", {GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT}}, + {"r32f", {GL_R32F, GL_RED, GL_FLOAT}}, + {"rg32f", {GL_RG32F, GL_RG, GL_FLOAT}}, + {"depth16", {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}}, + {"depth24", {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_FLOAT}}, + {"depth32", {GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_FLOAT}}, + {"depth32f", {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}}, + {"depth-stencil", { GL_DEPTH24_STENCIL8_EXT, GL_DEPTH_STENCIL_EXT, GL_FLOAT}} +}; + +PropStringMap wrap_mode_map = { + {"clamp", osg::Texture::CLAMP}, + {"clamp-to-edge", osg::Texture::CLAMP_TO_EDGE}, + {"clamp-to-border", osg::Texture::CLAMP_TO_BORDER}, + {"repeat", osg::Texture::REPEAT}, + {"mirror", osg::Texture::MIRROR} +}; + +PropStringMap filter_mode_map = { + {"linear", osg::Texture::LINEAR}, + {"linear-mipmap-linear", osg::Texture::LINEAR_MIPMAP_LINEAR}, + {"linear-mipmap-nearest", osg::Texture::LINEAR_MIPMAP_NEAREST}, + {"nearest", osg::Texture::NEAREST}, + {"nearest-mipmap-linear", osg::Texture::NEAREST_MIPMAP_LINEAR}, + {"nearest-mipmap-nearest", osg::Texture::NEAREST_MIPMAP_NEAREST} +}; + +PropStringMap shadow_texture_mode_map = { + {"luminance", osg::Texture::LUMINANCE}, + {"intensity", osg::Texture::INTENSITY}, + {"alpha", osg::Texture::ALPHA} +}; + +PropStringMap shadow_compare_func_map = { + {"never", osg::Texture::NEVER}, + {"less", osg::Texture::LESS}, + {"equal", osg::Texture::EQUAL}, + {"lequal", osg::Texture::LEQUAL}, + {"greater", osg::Texture::GREATER}, + {"notequal", osg::Texture::NOTEQUAL}, + {"gequal", osg::Texture::GEQUAL}, + {"always", osg::Texture::ALWAYS} +}; + +Buffer * +buildBuffer(Compositor *compositor, const SGPropertyNode *node) +{ + std::string type = node->getStringValue("type"); + if (type.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "buildBuffer: No type specified"); + return 0; + } + + osg::ref_ptr buffer = new Buffer; + osg::Texture *texture; + + int width = 0; + const SGPropertyNode *p_width = getPropertyChild(node, "width"); + if (p_width) { + if (p_width->getStringValue() == std::string("screen")) { + buffer->width_scale = 1.0f; + const SGPropertyNode *p_w_scale = getPropertyChild(node, "screen-width-scale"); + if (p_w_scale) + buffer->width_scale = p_w_scale->getFloatValue(); + width = buffer->width_scale * compositor->getViewport()->width(); + } else { + width = p_width->getIntValue(); + } + } + int height = 0; + const SGPropertyNode *p_height = getPropertyChild(node, "height"); + if (p_height) { + if (p_height->getStringValue() == std::string("screen")) { + buffer->height_scale = 1.0f; + const SGPropertyNode *p_h_scale = getPropertyChild(node, "screen-height-scale"); + if (p_h_scale) + buffer->height_scale = p_h_scale->getFloatValue(); + height = buffer->height_scale * compositor->getViewport()->height(); + } else { + height = p_height->getIntValue(); + } + } + int depth = 0; + const SGPropertyNode *p_depth = getPropertyChild(node, "depth"); + if (p_depth) + depth = p_depth->getIntValue(); + + if (type == "1d") { + osg::Texture1D *tex1D = new osg::Texture1D; + tex1D->setTextureWidth(width); + texture = tex1D; + } else if (type == "2d") { + osg::Texture2D *tex2D = new osg::Texture2D; + tex2D->setTextureSize(width, height); + texture = tex2D; + } else if (type == "2d-array") { + osg::Texture2DArray *tex2D_array = new osg::Texture2DArray; + tex2D_array->setTextureSize(width, height, depth); + texture = tex2D_array; + } else if (type == "2d-multisample") { + osg::Texture2DMultisample *tex2DMS = new osg::Texture2DMultisample; + tex2DMS->setTextureSize(width, height); + tex2DMS->setNumSamples(node->getIntValue("num-samples", 0)); + texture = tex2DMS; + } else if (type == "3d") { + osg::Texture3D *tex3D = new osg::Texture3D; + tex3D->setTextureSize(width, height, depth); + texture = tex3D; + } else if (type == "rect") { + osg::TextureRectangle *tex_rect = new osg::TextureRectangle; + tex_rect->setTextureSize(width, height); + texture = tex_rect; + } else if (type == "cubemap") { + osg::TextureCubeMap *tex_cubemap = new osg::TextureCubeMap; + tex_cubemap->setTextureSize(width, height); + texture = tex_cubemap; + } else { + SG_LOG(SG_INPUT, SG_ALERT, "Unknown texture type '" << type << "'"); + return 0; + } + buffer->texture = texture; + + bool resize_npot = node->getBoolValue("resize-npot", false); + texture->setResizeNonPowerOfTwoHint(resize_npot); + + BufferFormat format; + if (findPropString(node, "format", format, buffer_format_map)) { + texture->setInternalFormat(format.internal_format); + texture->setSourceFormat(format.source_format); + texture->setSourceType(format.source_type); + } else { + texture->setInternalFormat(GL_RGBA); + SG_LOG(SG_INPUT, SG_WARN, "Unknown buffer format specified, using RGBA"); + } + + osg::Texture::FilterMode filter_mode = osg::Texture::LINEAR; + findPropString(node, "min-filter", filter_mode, filter_mode_map); + texture->setFilter(osg::Texture::MIN_FILTER, filter_mode); + findPropString(node, "mag-filter", filter_mode, filter_mode_map); + texture->setFilter(osg::Texture::MAG_FILTER, filter_mode); + + osg::Texture::WrapMode wrap_mode = osg::Texture::CLAMP_TO_BORDER; + findPropString(node, "wrap-s", wrap_mode, wrap_mode_map); + texture->setWrap(osg::Texture::WRAP_S, wrap_mode); + findPropString(node, "wrap-t", wrap_mode, wrap_mode_map); + texture->setWrap(osg::Texture::WRAP_T, wrap_mode); + findPropString(node, "wrap-r", wrap_mode, wrap_mode_map); + texture->setWrap(osg::Texture::WRAP_R, wrap_mode); + + float anis = node->getFloatValue("anisotropy", 1.0f); + texture->setMaxAnisotropy(anis); + + osg::Vec4f border_color(0.0f, 0.0f, 0.0f, 0.0f); + const SGPropertyNode *p_border_color = node->getChild("border-color"); + if (p_border_color) + border_color = toOsg(p_border_color->getValue()); + texture->setBorderColor(border_color); + + bool shadow_comparison = node->getBoolValue("shadow-comparison", false); + texture->setShadowComparison(shadow_comparison); + if (shadow_comparison) { + osg::Texture::ShadowTextureMode shadow_texture_mode = + osg::Texture::LUMINANCE; + findPropString(node, "shadow-texture-mode", + shadow_texture_mode, shadow_texture_mode_map); + texture->setShadowTextureMode(shadow_texture_mode); + + osg::Texture::ShadowCompareFunc shadow_compare_func = + osg::Texture::LEQUAL; + findPropString(node, "shadow-compare-func", + shadow_compare_func, shadow_compare_func_map); + texture->setShadowCompareFunc(shadow_compare_func); + } + + return buffer.release(); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/CompositorBuffer.hxx b/simgear/scene/viewer/CompositorBuffer.hxx new file mode 100644 index 00000000..df0a92a4 --- /dev/null +++ b/simgear/scene/viewer/CompositorBuffer.hxx @@ -0,0 +1,46 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_BUFFER_HXX +#define SG_COMPOSITOR_BUFFER_HXX + +#include + +class SGPropertyNode; + +namespace simgear { +namespace compositor { + +class Compositor; + +struct Buffer : public osg::Referenced { + Buffer() : width_scale(0.0f), height_scale(0.0f) {} + + osg::ref_ptr texture; + + /** + * The amount to multiply the size of the default framebuffer. + * A factor of 0.0 means that the buffer has a fixed size. + */ + float width_scale, height_scale; +}; + +Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node); + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_BUFFER_HXX */ diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx new file mode 100644 index 00000000..8ee399d7 --- /dev/null +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -0,0 +1,694 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "CompositorPass.hxx" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ClusteredForward.hxx" +#include "Compositor.hxx" +#include "CompositorUtil.hxx" + +namespace simgear { +namespace compositor { + +PropStringMap buffer_component_map = { + {"color", osg::Camera::COLOR_BUFFER}, + {"color0", osg::Camera::COLOR_BUFFER0}, + {"color1", osg::Camera::COLOR_BUFFER1}, + {"color2", osg::Camera::COLOR_BUFFER2}, + {"color3", osg::Camera::COLOR_BUFFER3}, + {"color4", osg::Camera::COLOR_BUFFER4}, + {"color5", osg::Camera::COLOR_BUFFER5}, + {"color6", osg::Camera::COLOR_BUFFER6}, + {"color7", osg::Camera::COLOR_BUFFER7}, + {"depth", osg::Camera::DEPTH_BUFFER}, + {"stencil", osg::Camera::STENCIL_BUFFER}, + {"packed-depth-stencil", osg::Camera::PACKED_DEPTH_STENCIL_BUFFER} +}; + +Pass * +PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) +{ + // The pass index matches its render order + int render_order = root->getIndex(); + + osg::ref_ptr pass = new Pass; + pass->name = root->getStringValue("name"); + if (pass->name.empty()) { + SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << render_order + << " has no name. It won't be addressable by name!"); + } + pass->type = root->getStringValue("type"); + + std::string eff_override_file = root->getStringValue("effect-override"); + if (!eff_override_file.empty()) + pass->effect_override = makeEffect(eff_override_file, true, 0); + + osg::Camera *camera = new Camera; + pass->camera = camera; + + camera->setName(pass->name); + camera->setGraphicsContext(compositor->_gc); + // Even though this camera will be added as a slave to the view, it will + // always be updated manually in Compositor::update() + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + // Same with the projection matrix + camera->setProjectionResizePolicy(osg::Camera::FIXED); + camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + + // XXX: Should we make this configurable? + camera->setCullingMode(CullSettings::SMALL_FEATURE_CULLING + | CullSettings::VIEW_FRUSTUM_CULLING); + + osg::Node::NodeMask cull_mask = + std::stoul(root->getStringValue("cull-mask", "0xffffffff"), nullptr, 0); + pass->cull_mask = cull_mask; + camera->setCullMask(pass->cull_mask); + camera->setCullMaskLeft(pass->cull_mask); + camera->setCullMaskRight(pass->cull_mask); + + osg::Vec4f clear_color(0.0f, 0.0f, 0.0f, 0.0f); + const SGPropertyNode *p_clear_color = root->getChild("clear-color"); + if (p_clear_color) + clear_color = toOsg(p_clear_color->getValue()); + camera->setClearColor(clear_color); + osg::Vec4f clear_accum(0.0f, 0.0f, 0.0f, 0.0f); + const SGPropertyNode *p_clear_accum = root->getChild("clear-accum"); + if (p_clear_accum) + clear_accum = toOsg(p_clear_accum->getValue()); + camera->setClearAccum(clear_accum); + camera->setClearDepth(root->getFloatValue("clear-depth", 1.0f)); + camera->setClearStencil(root->getIntValue("clear-stencil", 0)); + + GLbitfield clear_mask = 0; + if (root->getBoolValue("clear-color-bit", true)) + clear_mask |= GL_COLOR_BUFFER_BIT; + if (root->getBoolValue("clear-accum-bit", false)) + clear_mask |= GL_ACCUM_BUFFER_BIT; + if (root->getBoolValue("clear-depth-bit", true)) + clear_mask |= GL_DEPTH_BUFFER_BIT; + if (root->getBoolValue("clear-stencil-bit", false)) + clear_mask |= GL_STENCIL_BUFFER_BIT; + // Default clear mask is GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, as in OSG + camera->setClearMask(clear_mask); + + PropertyList p_bindings = root->getChildren("binding"); + for (auto const &p_binding : p_bindings) { + if (!checkConditional(p_binding)) + continue; + try { + std::string buffer_name = p_binding->getStringValue("buffer"); + if (buffer_name.empty()) + throw sg_exception("No buffer specified"); + + Buffer *buffer = compositor->getBuffer(buffer_name); + if (!buffer) + throw sg_exception(std::string("Unknown buffer '") + + buffer_name + "'"); + + osg::Texture *texture = buffer->texture; + + int unit = p_binding->getIntValue("unit", -1); + if (unit < 0) + throw sg_exception("No texture unit specified"); + + // Make the texture available to every child of the pass, overriding + // existing units + camera->getOrCreateStateSet()->setTextureAttributeAndModes( + unit, + texture, + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } catch (sg_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping binding " + << p_binding->getIndex() << " in pass " << render_order + << ": " << e.what()); + } + } + + PropertyList p_attachments = root->getChildren("attachment"); + if (p_attachments.empty()) { + // If there are no attachments, assume the pass is rendering + // directly to the screen + + camera->setRenderOrder(osg::Camera::NESTED_RENDER, render_order * 10); + // OSG cameras use the framebuffer by default, but it is stated + // explicitly anyway + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + + camera->setDrawBuffer(GL_BACK); + camera->setReadBuffer(GL_BACK); + + // Use the physical viewport. We can't let the user choose the viewport + // size because some parts of the window might not be ours. + camera->setViewport(compositor->_viewport); + } else { + // This is a RTT camera + + camera->setRenderOrder(osg::Camera::PRE_RENDER, render_order * 10); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + + bool viewport_absolute = false; + // The index of the attachment to be used as the size of the viewport. + // The one with index 0 is used by default. + int viewport_attachment = 0; + const SGPropertyNode *p_viewport = root->getChild("viewport"); + if (p_viewport) { + // The user has manually specified a viewport size + viewport_absolute = p_viewport->getBoolValue("absolute", false); + if (viewport_absolute) { + camera->setViewport(p_viewport->getIntValue("x"), + p_viewport->getIntValue("y"), + p_viewport->getIntValue("width"), + p_viewport->getIntValue("height")); + } + viewport_attachment = p_viewport->getIntValue("use-attachment", 0); + if (!root->getChild("attachment", viewport_attachment)) { + // Let OSG manage the viewport automatically + camera->setViewport(new osg::Viewport); + SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Can't use attachment " + << viewport_attachment << " to resize the viewport"); + } + } + + for (auto const &p_attachment : p_attachments) { + if (!checkConditional(p_attachment)) + continue; + try { + std::string buffer_name = p_attachment->getStringValue("buffer"); + if (buffer_name.empty()) + throw sg_exception("No buffer specified"); + + Buffer *buffer = compositor->getBuffer(buffer_name); + if (!buffer) + throw sg_exception(std::string("Unknown buffer '") + + buffer_name + "'"); + + osg::Texture *texture = buffer->texture; + + osg::Camera::BufferComponent component = osg::Camera::COLOR_BUFFER; + findPropString(p_attachment, "component", component, buffer_component_map); + + unsigned int level = p_attachment->getIntValue("level", 0); + unsigned int face = p_attachment->getIntValue("face", 0); + bool mipmap_generation = + p_attachment->getBoolValue("mipmap-generation", false); + unsigned int multisample_samples = + p_attachment->getIntValue("multisample-samples", 0); + unsigned int multisample_color_samples = + p_attachment->getIntValue("multisample-color-samples", 0); + + camera->attach(component, + texture, + level, + face, + mipmap_generation, + multisample_samples, + multisample_color_samples); + + if (!viewport_absolute && + (p_attachment->getIndex() == viewport_attachment)) { + if ((buffer->width_scale == 0.0f) && + (buffer->height_scale == 0.0f)) { + // This is a fixed size pass. We allow the user to use + // relative coordinates to shape the viewport. + float x = p_viewport->getFloatValue("x", 0.0f); + float y = p_viewport->getFloatValue("y", 0.0f); + float width = p_viewport->getFloatValue("width", 1.0f); + float height = p_viewport->getFloatValue("height", 1.0f); + camera->setViewport(x * texture->getTextureWidth(), + y * texture->getTextureHeight(), + width * texture->getTextureWidth(), + height * texture->getTextureHeight()); + } else { + // This is a pass that should match the physical viewport + // size. Store the scales so we can resize the pass later + // if the physical viewport changes size. + pass->viewport_width_scale = buffer->width_scale; + pass->viewport_height_scale = buffer->height_scale; + camera->setViewport( + 0, + 0, + buffer->width_scale * compositor->_viewport->width(), + buffer->height_scale * compositor->_viewport->height()); + } + } + } catch (sg_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping attachment " + << p_attachment->getIndex() << " in pass " << render_order + << ": " << e.what()); + } + } + } + + return pass.release(); +} + +//------------------------------------------------------------------------------ + +struct QuadPassBuilder : public PassBuilder { +public: + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { + osg::ref_ptr pass = PassBuilder::build(compositor, root); + + osg::Camera *camera = pass->camera; + camera->setAllowEventFocus(false); + camera->setViewMatrix(osg::Matrix::identity()); + camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); + + float left = 0.0f, bottom = 0.0f, width = 1.0f, height = 1.0f, scale = 1.0f; + const SGPropertyNode *p_geometry = root->getNode("geometry"); + if (p_geometry) { + left = p_geometry->getFloatValue("left", left); + bottom = p_geometry->getFloatValue("bottom", bottom); + width = p_geometry->getFloatValue("width", width); + height = p_geometry->getFloatValue("height", height); + scale = p_geometry->getFloatValue("scale", scale); + } + + const std::string eff_file = root->getStringValue("effect"); + + osg::ref_ptr quad = createFullscreenQuad( + left, bottom, width, height, scale, eff_file); + camera->addChild(quad); + + osg::StateSet *ss = camera->getOrCreateStateSet(); + for (const auto &uniform : compositor->getUniforms()) + ss->addUniform(uniform); + + return pass.release(); + } +protected: + osg::Geode *createFullscreenQuad(float left, + float bottom, + float width, + float height, + float scale, + const std::string &eff_file) { + osg::Geometry *geom; + + // When the quad is fullscreen, it can be optimized by using a + // a fullscreen triangle instead of a quad to avoid discarding pixels + // in the diagonal. If the desired geometry does not occupy the entire + // viewport, this optimization does not occur and a normal quad is drawn + // instead. + if (left != 0.0f || bottom != 0.0f || width != 1.0f || height != 1.0f + || scale != 1.0f) { + geom = osg::createTexturedQuadGeometry( + osg::Vec3(left, bottom, 0.0f), + osg::Vec3(width, 0.0f, 0.0f), + osg::Vec3(0.0f, height, 0.0f), + 0.0f, 0.0f, scale, scale); + } else { + geom = new osg::Geometry; + + osg::Vec3Array *coords = new osg::Vec3Array(3); + (*coords)[0].set(0.0f, 2.0f, 0.0f); + (*coords)[1].set(0.0f, 0.0f, 0.0f); + (*coords)[2].set(2.0f, 0.0f, 0.0f); + geom->setVertexArray(coords); + + osg::Vec2Array *tcoords = new osg::Vec2Array(3); + (*tcoords)[0].set(0.0f, 2.0f); + (*tcoords)[1].set(0.0f, 0.0f); + (*tcoords)[2].set(2.0f, 0.0f); + geom->setTexCoordArray(0, tcoords); + + osg::Vec4Array *colours = new osg::Vec4Array(1); + (*colours)[0].set(1.0f, 1.0f, 1.0, 1.0f); + geom->setColorArray(colours, osg::Array::BIND_OVERALL); + + osg::Vec3Array *normals = new osg::Vec3Array(1); + (*normals)[0].set(0.0f, 0.0f, 1.0f); + geom->setNormalArray(normals, osg::Array::BIND_OVERALL); + + geom->addPrimitiveSet(new osg::DrawArrays( + osg::PrimitiveSet::TRIANGLES, 0, 3)); + } + + osg::ref_ptr quad = new EffectGeode; + if (!eff_file.empty()) { + Effect *eff = makeEffect(eff_file, true, 0); + if (eff) + quad->setEffect(eff); + } + quad->addDrawable(geom); + quad->setCullingActive(false); + + osg::ref_ptr quad_state = quad->getOrCreateStateSet(); + int values = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; + quad_state->setAttribute(new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, + osg::PolygonMode::FILL), + values); + quad_state->setMode(GL_LIGHTING, values); + quad_state->setMode(GL_DEPTH_TEST, values); + + return quad.release(); + } +}; +RegisterPassBuilder registerQuadPass("quad"); + +//------------------------------------------------------------------------------ + +class LightFinder : public osg::NodeVisitor { +public: + LightFinder(const std::string &name) : + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _name(name) {} + virtual void apply(osg::Node &node) { + // Only traverse the scene graph if we haven't found a light yet (or if + // the one we found earlier is no longer valid). + if (getLight().valid()) + return; + + if (node.getName() == _name) { + osg::LightSource *light_source = + dynamic_cast(&node); + if (light_source) + _light = light_source->getLight(); + } + + traverse(node); + } + osg::ref_ptr getLight() const { + osg::ref_ptr light_ref; + _light.lock(light_ref); + return light_ref; + } +protected: + std::string _name; + osg::observer_ptr _light; +}; + +struct ShadowMapUpdateCallback : public Pass::PassUpdateCallback { +public: + ShadowMapUpdateCallback(const std::string &light_name, + float near_m, float far_m, + const std::string &suffix, + int sm_width, int sm_height) : + _light_finder(new LightFinder(light_name)), + _near_m(near_m), + _far_m(far_m) { + _light_matrix_uniform = new osg::Uniform( + osg::Uniform::FLOAT_MAT4, std::string("fg_LightMatrix_") + suffix); + _half_sm_size = osg::Vec2d((double)sm_width, (double)sm_height) / 2.0; + } + virtual void updatePass(Pass &pass, + const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) { + osg::Camera *camera = pass.camera; + // Look for the light + camera->accept(*_light_finder); + osg::ref_ptr light = _light_finder->getLight(); + if (!light) { + // We could not find any light + return; + } + osg::Vec4 light_pos = light->getPosition(); + if (light_pos.w() != 0.0) { + // We only support directional light sources for now + return; + } + osg::Vec3 light_dir = + osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z()); + + // The light direction we've just queried is from the previous frame. + // This is because the position of the osg::LightSource gets updated + // during the update traversal, and this function happens before that + // in the SubsystemMgr update. + // This is not a problem though (for now). + + osg::Matrix view_inverse = osg::Matrix::inverse(view_matrix); + + // Calculate the light's point of view transformation matrices. + // Taken from Project Rembrandt. + double left, right, bottom, top, zNear, zFar; + proj_matrix.getFrustum(left, right, bottom, top, zNear, zFar); + + osg::BoundingSphere bs; + bs.expandBy(osg::Vec3(left, bottom, -zNear) * (_near_m / zNear)); + bs.expandBy(osg::Vec3(right, top, -zNear) * (_far_m / zNear)); + bs.expandBy(osg::Vec3(left, bottom, -zNear) * (_far_m / zNear)); + bs.expandBy(osg::Vec3(right, top, -zNear) * (_near_m / zNear)); + + osg::Vec4 aim4 = osg::Vec4(bs.center(), 1.0) * view_inverse; + osg::Vec3 aim(aim4.x(), aim4.y(), aim4.z()); + osg::Vec3 up(0.0f, 1.0f, 0.0f); + + osg::Matrixd &light_view_matrix = camera->getViewMatrix(); + light_view_matrix.makeLookAt( + aim + (light_dir * bs.radius() * 2.0f), + aim, + aim); + + osg::Matrixd &light_proj_matrix = camera->getProjectionMatrix(); + light_proj_matrix.makeOrtho( + -bs.radius(), bs.radius(), + -bs.radius(), bs.radius(), + -bs.radius() * 6.0f, bs.radius() * 6.0f); + + // Do texel snapping to prevent flickering or shimmering. + // We are using double precision vectors and matrices because in FG + // world coordinates are relative to the center of the Earth, which can + // (and will) cause precision issues due to their magnitude. + osg::Vec4d shadow_origin4 = osg::Vec4d(0.0, 0.0, 0.0, 1.0) * + light_view_matrix * light_proj_matrix; + osg::Vec2d shadow_origin(shadow_origin4.x(), shadow_origin4.y()); + shadow_origin = osg::Vec2d(shadow_origin.x() * _half_sm_size.x(), + shadow_origin.y() * _half_sm_size.y()); + osg::Vec2d rounded_origin(std::round(shadow_origin.x()), + std::round(shadow_origin.y())); + osg::Vec2d rounding = rounded_origin - shadow_origin; + rounding = osg::Vec2d(rounding.x() / _half_sm_size.x(), + rounding.y() / _half_sm_size.y()); + + osg::Matrixd round_matrix = osg::Matrixd::translate( + rounding.x(), rounding.y(), 0.0); + light_proj_matrix *= round_matrix; + + osg::Matrixf light_matrix = + // Include the real camera inverse view matrix because if the shader + // used world coordinates, there would be precision issues. + view_inverse * + camera->getViewMatrix() * + camera->getProjectionMatrix() * + // Bias matrices + osg::Matrix::translate(1.0, 1.0, 1.0) * + osg::Matrix::scale(0.5, 0.5, 0.5); + _light_matrix_uniform->set(light_matrix); + } + + osg::Uniform *getLightMatrixUniform() const { + return _light_matrix_uniform.get(); + } +protected: + osg::ref_ptr _light_finder; + float _near_m; + float _far_m; + osg::ref_ptr _light_matrix_uniform; + osg::Vec2d _half_sm_size; +}; + +struct ShadowMapPassBuilder : public PassBuilder { + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { + osg::ref_ptr pass = PassBuilder::build(compositor, root); + pass->useMastersSceneData = true; + + osg::Camera *camera = pass->camera; + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + + std::string light_name = root->getStringValue("light-name"); + float near_m = root->getFloatValue("near-m"); + float far_m = root->getFloatValue("far-m"); + int sm_width = camera->getViewport()->width(); + int sm_height = camera->getViewport()->height(); + pass->update_callback = new ShadowMapUpdateCallback( + light_name, + near_m, far_m, + pass->name, + sm_width, sm_height); + + return pass.release(); + } +}; +RegisterPassBuilder registerShadowMapPass("shadow-map"); + +//------------------------------------------------------------------------------ + +class SceneUpdateCallback : public Pass::PassUpdateCallback { +public: + SceneUpdateCallback(int cubemap_face, float zNear, float zFar) : + _cubemap_face(cubemap_face), + _zNear(zNear), + _zFar(zFar) {} + + virtual void updatePass(Pass &pass, + const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) { + osg::Camera *camera = pass.camera; + if (_cubemap_face < 0) { + camera->setViewMatrix(view_matrix); + camera->setProjectionMatrix(proj_matrix); + } else { + osg::Vec3 camera_pos = osg::Vec3(0.0, 0.0, 0.0) * + osg::Matrix::inverse(view_matrix); + + typedef std::pair CubemapFace; + const CubemapFace id[] = { + CubemapFace(osg::Vec3( 1, 0, 0), osg::Vec3( 0, -1, 0)), // +X + CubemapFace(osg::Vec3(-1, 0, 0), osg::Vec3( 0, -1, 0)), // -X + CubemapFace(osg::Vec3( 0, 1, 0), osg::Vec3( 0, 0, 1)), // +Y + CubemapFace(osg::Vec3( 0, -1, 0), osg::Vec3( 0, 0, -1)), // -Y + CubemapFace(osg::Vec3( 0, 0, 1), osg::Vec3( 0, -1, 0)), // +Z + CubemapFace(osg::Vec3( 0, 0, -1), osg::Vec3( 0, -1, 0)) // -Z + }; + + osg::Matrix cubemap_view_matrix; + cubemap_view_matrix.makeLookAt(camera_pos, + camera_pos + id[_cubemap_face].first, + camera_pos + id[_cubemap_face].second); + camera->setViewMatrix(cubemap_view_matrix); + camera->setProjectionMatrixAsFrustum(-1.0, 1.0, -1.0, 1.0, + 1.0, 10000.0); + } + + if (_zNear != 0.0f && _zFar != 0.0f) { + osg::Matrix new_proj; + makeNewProjMat(camera->getProjectionMatrix(), + _zNear, _zFar, new_proj); + camera->setProjectionMatrix(new_proj); + } + } +protected: + // Given a projection matrix, return a new one with the same frustum + // sides and new near / far values. + void makeNewProjMat(Matrixd& oldProj, double znear, + double zfar, Matrixd& projection) { + projection = oldProj; + // Slightly inflate the near & far planes to avoid objects at the + // extremes being clipped out. + znear *= 0.999; + zfar *= 1.001; + + // Clamp the projection matrix z values to the range (near, far) + double epsilon = 1.0e-6; + if (fabs(projection(0,3)) < epsilon && + fabs(projection(1,3)) < epsilon && + fabs(projection(2,3)) < epsilon) { + // Projection is Orthographic + epsilon = -1.0/(zfar - znear); // Used as a temp variable + projection(2,2) = 2.0*epsilon; + projection(3,2) = (zfar + znear)*epsilon; + } else { + // Projection is Perspective + double trans_near = (-znear*projection(2,2) + projection(3,2)) / + (-znear*projection(2,3) + projection(3,3)); + double trans_far = (-zfar*projection(2,2) + projection(3,2)) / + (-zfar*projection(2,3) + projection(3,3)); + double ratio = fabs(2.0/(trans_near - trans_far)); + double center = -0.5*(trans_near + trans_far); + + projection.postMult(osg::Matrixd(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, ratio, 0.0, + 0.0, 0.0, center*ratio, 1.0)); + } + } + + int _cubemap_face; + float _zNear; + float _zFar; +}; + +struct ScenePassBuilder : public PassBuilder { +public: + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { + osg::ref_ptr pass = PassBuilder::build(compositor, root); + pass->useMastersSceneData = true; + pass->inherit_cull_mask = true; + + osg::Camera *camera = pass->camera; + camera->setAllowEventFocus(true); + + const SGPropertyNode *clustered = root->getChild("clustered-forward"); + if (clustered) { + camera->setInitialDrawCallback(new ClusteredForwardDrawCallback); + } + + int cubemap_face = root->getIntValue("cubemap-face", -1); + float zNear = root->getFloatValue("z-near", 0.0f); + float zFar = root->getFloatValue("z-far", 0.0f); + pass->update_callback = new SceneUpdateCallback(cubemap_face, zNear, zFar); + + std::string shadow_pass_name = root->getStringValue("use-shadow-pass"); + if (!shadow_pass_name.empty()) { + Pass *shadow_pass = compositor->getPass(shadow_pass_name); + if (shadow_pass) { + ShadowMapUpdateCallback *updatecb = + dynamic_cast( + shadow_pass->update_callback.get()); + if (updatecb) { + camera->getOrCreateStateSet()->addUniform( + updatecb->getLightMatrixUniform()); + } else { + SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Pass '" + << shadow_pass_name << "is not a shadow pass"); + } + } else { + SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not " + "find shadow pass named '" << shadow_pass_name << "'"); + } + } + + return pass.release(); + } +}; + +RegisterPassBuilder registerScenePass("scene"); + +//------------------------------------------------------------------------------ + +Pass * +buildPass(Compositor *compositor, const SGPropertyNode *root) +{ + std::string type = root->getStringValue("type"); + if (type.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "buildPass: Unspecified pass type"); + return 0; + } + PassBuilder *builder = PassBuilder::find(type); + if (!builder) { + SG_LOG(SG_INPUT, SG_ALERT, "buildPass: Unknown pass type '" + << type << "'"); + return 0; + } + + return builder->build(compositor, root); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/CompositorPass.hxx b/simgear/scene/viewer/CompositorPass.hxx new file mode 100644 index 00000000..68fe89e3 --- /dev/null +++ b/simgear/scene/viewer/CompositorPass.hxx @@ -0,0 +1,133 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_PASS_HXX +#define SG_COMPOSITOR_PASS_HXX + +#include + +#include +#include + +#include +#include +#include + +namespace simgear { +namespace compositor { + +class Compositor; + +/** + * A Pass encapsulates a single render operation. In an OSG context, this is + * best represented as a Camera attached to the Viewer as a slave camera. + * + * Passes can render directly to the framebuffer or to a texture via FBOs. Also, + * the OpenGL state can be modified via the Effects framework and by exposing RTT + * textures from previous passes. + * + * Every pass can be enabled and disabled via a property tree conditional + * expression. This allows dynamic rendering pipelines where features can be + * enabled or disabled in a coherent way by the user. + */ +struct Pass : public osg::Referenced { + Pass() : + useMastersSceneData(false), + cull_mask(0xffffff), + inherit_cull_mask(false), + viewport_width_scale(0.0f), + viewport_height_scale(0.0f) {} + + std::string name; + std::string type; + osg::ref_ptr camera; + /** If null, there is no effect override for this pass. */ + osg::ref_ptr effect_override; + bool useMastersSceneData; + osg::Node::NodeMask cull_mask; + /** Whether the cull mask is ANDed with the view master camera cull mask. */ + bool inherit_cull_mask; + float viewport_width_scale; + float viewport_height_scale; + + struct PassUpdateCallback : public virtual osg::Referenced { + public: + virtual void updatePass(Pass &pass, + const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) = 0; + }; + + osg::ref_ptr update_callback; +}; + +class PassBuilder : public SGReferenced { +public: + virtual ~PassBuilder() {} + + /** + * \brief Build a pass. + * + * By default, this function implements commonly used features such as + * input/output buffers, conditional support etc., but can be safely ignored + * and overrided for more special passes. + * + * @param compositor The Compositor instance that owns the pass. + * @param The root node of the pass property tree. + * @return A Pass or a null pointer if an error occurred. + */ + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root); + + static PassBuilder *find(const std::string &type) { + auto itr = PassBuilderMapSingleton::instance()->_map.find(type); + if (itr == PassBuilderMapSingleton::instance()->_map.end()) + return 0; + return itr->second.ptr(); + } +protected: + typedef std::unordered_map> PassBuilderMap; + struct PassBuilderMapSingleton : public Singleton { + PassBuilderMap _map; + }; + template + friend struct RegisterPassBuilder; +}; + +/** + * An instance of this type registers a new pass type T with a name. + * A global instance of this class must be created in CompositorPass.cxx to + * register a new pass type. + */ +template +struct RegisterPassBuilder { + RegisterPassBuilder(const std::string &name) { + PassBuilder::PassBuilderMapSingleton::instance()-> + _map.insert(std::make_pair(name, new T)); + } +}; + +/** + * \brief Create a pass from a property tree definition. + * + * @param comp The Compositor instance that owns the pass. + * @param node The root node of the pass property tree. + * @return A Pass or a null pointer if an error occurred. + */ +Pass *buildPass(Compositor *compositor, const SGPropertyNode *root); + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_PASS_HXX */ diff --git a/simgear/scene/viewer/CompositorUtil.cxx b/simgear/scene/viewer/CompositorUtil.cxx new file mode 100644 index 00000000..9595e7f3 --- /dev/null +++ b/simgear/scene/viewer/CompositorUtil.cxx @@ -0,0 +1,62 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include +#include +#include + +#include "CompositorUtil.hxx" + +namespace simgear { +namespace compositor { + +bool +checkConditional(const SGPropertyNode *node) +{ + const SGPropertyNode *p_condition = node->getChild("condition"); + if (!p_condition) + return true; + SGSharedPtr condition = + sgReadCondition(getPropertyRoot(), p_condition); + return !condition || condition->test(); +} + +const SGPropertyNode * +getPropertyNode(const SGPropertyNode *prop) +{ + if (!prop) + return 0; + if (prop->nChildren() > 0) { + const SGPropertyNode *propertyProp = prop->getChild("property"); + if (!propertyProp) + return prop; + return getPropertyRoot()->getNode(propertyProp->getStringValue()); + } + return prop; +} + +const SGPropertyNode * +getPropertyChild(const SGPropertyNode *prop, + const char *name) +{ + const SGPropertyNode *child = prop->getChild(name); + if (!child) + return 0; + return getPropertyNode(child); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/CompositorUtil.hxx b/simgear/scene/viewer/CompositorUtil.hxx new file mode 100644 index 00000000..2b0d47c9 --- /dev/null +++ b/simgear/scene/viewer/CompositorUtil.hxx @@ -0,0 +1,72 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_UTIL_HXX +#define SG_COMPOSITOR_UTIL_HXX + +#include + +namespace simgear { +namespace compositor { + +/** + * Lookup table that ties a string property value to a type that cannot be + * represented in the property tree. Useful for OSG or OpenGL enums. + */ +template +using PropStringMap = std::unordered_map; + +template +bool findPropString(const std::string &str, + T &value, + const PropStringMap &map) +{ + auto itr = map.find(str); + if (itr == map.end()) + return false; + + value = itr->second; + return true; +} + +template +bool findPropString(const SGPropertyNode *parent, + const std::string &child_name, + T &value, + const PropStringMap &map) +{ + const SGPropertyNode *child = parent->getNode(child_name); + if (child) { + if (findPropString(child->getStringValue(), value, map)) + return true; + } + return false; +} + +/** + * Check if node should be enabled based on a condition tag. + * If no condition tag is found inside or it is malformed, it will be enabled. + */ +bool checkConditional(const SGPropertyNode *node); + +const SGPropertyNode *getPropertyNode(const SGPropertyNode *prop); +const SGPropertyNode *getPropertyChild(const SGPropertyNode *prop, + const char *name); + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_UTIL_HXX */ diff --git a/simgear/structure/CMakeLists.txt b/simgear/structure/CMakeLists.txt index 5d81ad67..fb13114d 100644 --- a/simgear/structure/CMakeLists.txt +++ b/simgear/structure/CMakeLists.txt @@ -39,7 +39,7 @@ set(SOURCES commands.cxx event_mgr.cxx exception.cxx - subsystem_mgr.cxx + subsystem_mgr.cxx StateMachine.cxx ) @@ -63,6 +63,10 @@ add_executable(test_shared_ptr shared_ptr_test.cpp) target_link_libraries(test_shared_ptr ${TEST_LIBS}) add_test(shared_ptr ${EXECUTABLE_OUTPUT_PATH}/test_shared_ptr) +add_executable(test_commands test_commands.cxx) +target_link_libraries(test_commands ${TEST_LIBS}) +add_test(subsystems ${EXECUTABLE_OUTPUT_PATH}/test_commands) + endif(ENABLE_TESTS) add_boost_test(function_list diff --git a/simgear/structure/SGPerfMon.cxx b/simgear/structure/SGPerfMon.cxx index ae8191b5..c4d7caed 100644 --- a/simgear/structure/SGPerfMon.cxx +++ b/simgear/structure/SGPerfMon.cxx @@ -51,7 +51,10 @@ SGPerformanceMonitor::bind(void) { _statiticsSubsystems = _root->getChild("subsystems", 0, true); _statisticsFlag = _root->getChild("enabled", 0, true); + _timingDetailsFlag = _root->getChild("dump-stats", 0, true); + _timingDetailsFlag->setBoolValue(false); _statisticsInterval = _root->getChild("interval-s", 0, true); + _maxTimePerFrame_ms = _root->getChild("max-time-per-frame-ms", 0, true); } void @@ -60,6 +63,7 @@ SGPerformanceMonitor::unbind(void) _statiticsSubsystems = 0; _statisticsFlag = 0; _statisticsInterval = 0; + _maxTimePerFrame_ms = 0; } void @@ -83,7 +87,10 @@ SGPerformanceMonitor::update(double dt) else _subSysMgr->setReportTimingCb(this,0); } - + if (_timingDetailsFlag->getBoolValue()) { + _subSysMgr->setReportTimingStats(true); + _timingDetailsFlag->setBoolValue(false); + } if (!_isEnabled) return; @@ -94,6 +101,9 @@ SGPerformanceMonitor::update(double dt) _subSysMgr->reportTiming(); _lastUpdate.stamp(); } + if (_maxTimePerFrame_ms) { + SGSubsystem::maxTimePerFrame_ms = _maxTimePerFrame_ms->getIntValue(); + } } /** Callback hooked into the subsystem manager. */ diff --git a/simgear/structure/SGPerfMon.hxx b/simgear/structure/SGPerfMon.hxx index d7e4ed59..32d642cc 100644 --- a/simgear/structure/SGPerfMon.hxx +++ b/simgear/structure/SGPerfMon.hxx @@ -52,8 +52,10 @@ private: SGSubsystemMgr* _subSysMgr; SGPropertyNode_ptr _root; SGPropertyNode_ptr _statiticsSubsystems; + SGPropertyNode_ptr _timingDetailsFlag; SGPropertyNode_ptr _statisticsFlag; SGPropertyNode_ptr _statisticsInterval; + SGPropertyNode_ptr _maxTimePerFrame_ms; bool _isEnabled; int _count; diff --git a/simgear/structure/SGWeakReferenced.hxx b/simgear/structure/SGWeakReferenced.hxx index 3bb70ffa..24db41b8 100644 --- a/simgear/structure/SGWeakReferenced.hxx +++ b/simgear/structure/SGWeakReferenced.hxx @@ -22,7 +22,11 @@ #include "SGSharedPtr.hxx" #include +#if BOOST_VERSION >= 105600 +#include +#else #include +#endif #ifdef _MSC_VER # pragma warning(push) diff --git a/simgear/structure/StateMachine.cxx b/simgear/structure/StateMachine.cxx index 2bff0f3f..e29fefb1 100644 --- a/simgear/structure/StateMachine.cxx +++ b/simgear/structure/StateMachine.cxx @@ -20,7 +20,7 @@ */ #include - + #include "StateMachine.hxx" #include @@ -32,13 +32,13 @@ #include #include #include - + namespace simgear { typedef std::vector StatePtrVec; -static void readBindingList(SGPropertyNode* desc, const std::string& name, +static void readBindingList(SGPropertyNode* desc, const std::string& name, SGPropertyNode* root, SGBindingList& result) { for (auto b : desc->getChildren(name)) { @@ -54,8 +54,8 @@ class StateMachine::State::StatePrivate { public: std::string _name; - SGBindingList _updateBindings, - _entryBindings, + SGBindingList _updateBindings, + _entryBindings, _exitBindings; }; @@ -71,14 +71,14 @@ public: bool _excludeTarget; SGSharedPtr _condition; }; - + /////////////////////////////////////////////////////////////////////////// class StateMachine::StateMachinePrivate : public SGPropertyChangeListener { public: StateMachinePrivate(StateMachine* p) : _p(p) { } - + void computeEligibleTransitions() { _eligible.clear(); @@ -88,7 +88,7 @@ public: } } } - + StateMachine* _p; bool _initialised; State_ptr _currentState; @@ -96,14 +96,14 @@ public: std::vector _transitions; std::vector _eligible; SGTimeStamp _timeInState; - + bool _listenerLockout; ///< block our listener when self-updating props virtual void valueChanged(SGPropertyNode* changed) { if (_listenerLockout) { return; } - + if (changed == _currentStateIndex) { State_ptr s = _p->stateByIndex(changed->getIntValue()); _p->changeToState(s); @@ -111,7 +111,7 @@ public: _p->changeToStateName(changed->getStringValue()); } } - + // exposed properties SGPropertyNode_ptr _root; SGPropertyNode_ptr _currentStateIndex; @@ -144,12 +144,12 @@ void StateMachine::State::update() void StateMachine::State::fireEntryBindings() { fireBindingList(d->_entryBindings); -} +} void StateMachine::State::fireExitBindings() { fireBindingList(d->_exitBindings); -} +} void StateMachine::State::addUpdateBinding(SGBinding* aBinding) { @@ -184,38 +184,38 @@ StateMachine::Transition::~Transition() StateMachine::State* StateMachine::Transition::target() const { return d->_target; -} +} void StateMachine::Transition::addSourceState(State* aSource) { if (aSource == d->_target) { // should this be disallowed outright? SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source"); } - + d->_sourceStates.insert(aSource); -} +} bool StateMachine::Transition::applicableForState(State* aCurrent) const { if (d->_excludeTarget && (aCurrent == d->_target)) { return false; } - + if (d->_sourceStates.empty()) { return true; } return d->_sourceStates.count(aCurrent); -} - +} + bool StateMachine::Transition::evaluate() const { return d->_condition->test(); -} +} void StateMachine::Transition::fireBindings() { fireBindingList(d->_bindings); -} +} std::string StateMachine::Transition::name() const { @@ -231,12 +231,12 @@ void StateMachine::Transition::addBinding(SGBinding* aBinding) { d->_bindings.push_back(aBinding); } - + void StateMachine::Transition::setExcludeTarget(bool aExclude) { d->_excludeTarget = aExclude; } - + /////////////////////////////////////////////////////////////////////////// StateMachine::StateMachine() : @@ -249,7 +249,7 @@ StateMachine::StateMachine() : StateMachine::~StateMachine() { - + } void StateMachine::init() @@ -257,23 +257,23 @@ void StateMachine::init() if (d->_initialised) { return; } - + if (d->_states.empty()) { throw sg_range_exception("StateMachine::init: no states defined"); } - + d->_currentStateIndex = d->_root->getChild("current-index", 0, true); d->_currentStateIndex->setIntValue(0); - + d->_currentStateName = d->_root->getChild("current-name", 0, true); d->_currentStateName->setStringValue(""); - + d->_currentStateIndex->addChangeListener(d.get()); d->_currentStateName->addChangeListener(d.get()); - + d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true); d->_timeInStateProp->setIntValue(0); - + // TODO go to default state if found innerChangeState(d->_states[0], NULL); d->_initialised = true; @@ -283,20 +283,23 @@ void StateMachine::shutdown() { d->_currentStateIndex->removeChangeListener(d.get()); d->_currentStateName->removeChangeListener(d.get()); - + } void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans) { if (d->_currentState) { d->_currentState->fireExitBindings(); + SG_LOG(SG_GENERAL, SG_INFO, "Changing from state " << d->_currentState->name() << " to state:" << aState->name()); + } else { + SG_LOG(SG_GENERAL, SG_INFO, "Initializing to state:" << aState->name()); } - -// fire bindings before we change the state, hmmmm + +// fire bindings before we change the state, hmmmm if (aTrans) { aTrans->fireBindings(); } - + // update our private state and properties d->_listenerLockout = true; d->_currentState = aState; @@ -305,11 +308,11 @@ void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans) d->_currentStateIndex->setIntValue(indexOfState(aState)); d->_timeInStateProp->setIntValue(0); d->_listenerLockout = false; - + // fire bindings d->_currentState->fireEntryBindings(); d->_currentState->update(); - + d->computeEligibleTransitions(); } @@ -319,11 +322,11 @@ void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent) if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) { throw sg_exception("Requested change to state not in machine"); } - + if (aOnlyIfDifferent && (aState == d->_currentState)) { return; } - + innerChangeState(aState, NULL); } @@ -333,7 +336,7 @@ void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDiffe if (!st) { throw sg_range_exception("unknown state:" + aName); } - + changeToState(st, aOnlyIfDifferent); } @@ -341,7 +344,7 @@ StateMachine::State_ptr StateMachine::state() const { return d->_currentState; } - + SGPropertyNode* StateMachine::root() { return d->_root; @@ -352,27 +355,27 @@ void StateMachine::update(double aDt) // do this first, for triggers which depend on time in current state // (spring-loaded transitions) d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec()); - + Transition_ptr trigger; - + for (auto trans : d->_eligible) { if (trans->evaluate()) { if (trigger != Transition_ptr()) { - SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! " + SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! " << trans->name() << " or " << trigger->name()); } - + trigger = trans; } } - + if (trigger != Transition_ptr()) { SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name()); innerChangeState(trigger->target(), trigger); } - + d->_currentState->update(); -} +} StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const { @@ -381,7 +384,7 @@ StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) return sp; } } - + SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName); return State_ptr(); } @@ -391,17 +394,17 @@ StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const if (aIndex >= d->_states.size()) { throw sg_range_exception("invalid state index, out of bounds"); } - + return d->_states[aIndex]; } - + int StateMachine::indexOfState(State_ptr aState) const { StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState); if (it == d->_states.end()) { return -1; } - + return it - d->_states.begin(); } @@ -410,7 +413,7 @@ StateMachine::State_ptr StateMachine::createState(const std::string& aName) if (findStateByName(aName) != NULL) { throw sg_range_exception("duplicate state name"); } - + State_ptr st = new State(aName); addState(st); return st; @@ -431,38 +434,83 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root) d->_root = root->getNode(path, 0, true); assert(d->_root); } - + + int stateCount = 0; for (auto stateDesc : desc->getChildren("state")) { + stateCount++; std::string nm = stateDesc->getStringValue("name"); + + if (nm.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "No name found for state in branch " << path); + throw sg_exception("No name element in state"); + } + State_ptr st(new State(nm)); - + readBindingList(stateDesc, "enter", root, st->d->_entryBindings); readBindingList(stateDesc, "update", root, st->d->_updateBindings); readBindingList(stateDesc, "exit", root, st->d->_exitBindings); - + addState(st); } // of states iteration - + + if (stateCount < 2) { + SG_LOG(SG_GENERAL, SG_ALERT, "Fewer than two state elements found in branch " << path); + throw sg_exception("Fewer than two state elements found."); + } + for (auto tDesc : desc->getChildren("transition")) { std::string nm = tDesc->getStringValue("name"); - State_ptr target = findStateByName(tDesc->getStringValue("target")); - + std::string target_id = tDesc->getStringValue("target"); + + if (nm.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "No name found for transition in branch " << path); + throw sg_exception("No name element in transition"); + } + + if (target_id.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "No target element in transition " + << nm << " in state branch " << path); + throw sg_exception("No target element in transition"); + } + + State_ptr target = findStateByName(target_id); + + if (target == NULL) { + SG_LOG(SG_GENERAL, SG_ALERT, "Unknown target state " << target_id << " in transition " + << nm << " in state branch " << path); + throw sg_exception("No condition element in transition"); + } + + if (tDesc->getChild("condition") == NULL) { + SG_LOG(SG_GENERAL, SG_ALERT, "No condition element in transition " + << nm << " in state branch " << path); + throw sg_exception("No condition element in transition"); + } + SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition")); - + Transition_ptr t(new Transition(nm, target)); t->setTriggerCondition(cond); - + t->setExcludeTarget(tDesc->getBoolValue("exclude-target", true)); for (auto src : tDesc->getChildren("source")) { State_ptr srcState = findStateByName(src->getStringValue()); + + if (srcState == NULL) { + SG_LOG(SG_GENERAL, SG_ALERT, "Unknown source state " << src->getStringValue() << " in transition " + << nm << " in state branch " << path); + throw sg_exception("No condition element in transition"); + } + t->addSourceState(srcState); } - + readBindingList(tDesc, "binding", root, t->d->_bindings); - + addTransition(t); } // of states iteration - + init(); } diff --git a/simgear/structure/commands.cxx b/simgear/structure/commands.cxx index 150e16aa..5621c8d8 100644 --- a/simgear/structure/commands.cxx +++ b/simgear/structure/commands.cxx @@ -4,9 +4,7 @@ // // $Id$ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include #include @@ -20,23 +18,46 @@ #include #include +struct Invocation +{ + std::string command; + SGPropertyNode_ptr args; +}; + +class SGCommandMgr::Private +{ +public: + SGPropertyNode_ptr _rootNode; + + using InvocactionVec = std::vector; + InvocactionVec _queue; + + SGMutex _mutex; + + using command_map = std::map ; + command_map _commands; + + long _mainThreadId = 0; +}; //////////////////////////////////////////////////////////////////////// // Implementation of SGCommandMgr class. //////////////////////////////////////////////////////////////////////// -static SGCommandMgr* static_instance = NULL; +static SGCommandMgr* static_instance = nullptr; -SGCommandMgr::SGCommandMgr () +SGCommandMgr::SGCommandMgr () : + d(new Private) { - assert(static_instance == NULL); + d->_mainThreadId = SGThread::current(); + assert(static_instance == nullptr); static_instance = this; } SGCommandMgr::~SGCommandMgr () { assert(static_instance == this); - static_instance = NULL; + static_instance = nullptr; } SGCommandMgr* @@ -45,28 +66,37 @@ SGCommandMgr::instance() return static_instance ? static_instance : new SGCommandMgr; } +void SGCommandMgr::setImplicitRoot(SGPropertyNode *root) +{ + assert(root); + d->_rootNode = root; +} + void SGCommandMgr::addCommandObject (const std::string &name, Command* command) { - if (_commands.find(name) != _commands.end()) - throw sg_exception("duplicate command name:" + name); - - _commands[name] = command; +#if !defined(NDEBUG) + assert(SGThread::current() == d->_mainThreadId); +#endif + if (d->_commands.find(name) != d->_commands.end()) + throw sg_exception("duplicate command name:" + name); + + d->_commands[name] = command; } SGCommandMgr::Command* SGCommandMgr::getCommand (const std::string &name) const { - const command_map::const_iterator it = _commands.find(name); - return (it != _commands.end() ? it->second : 0); + const auto it = d->_commands.find(name); + return (it != d->_commands.end() ? it->second : nullptr); } string_list SGCommandMgr::getCommandNames () const { string_list names; - command_map::const_iterator it = _commands.begin(); - command_map::const_iterator last = _commands.end(); + auto it = d->_commands.begin(); + auto last = d->_commands.end(); while (it != last) { names.push_back(it->first); ++it; @@ -77,13 +107,24 @@ SGCommandMgr::getCommandNames () const bool SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const { +#if !defined(NDEBUG) + if (SGThread::current() != d->_mainThreadId) { + SG_LOG(SG_GENERAL, SG_WARN, "calling SGCommandMgr::execute from a different thread than expected. Command is:" << name); + } + // assert(SGThread::current() == d->_mainThreadId); +#endif Command* command = getCommand(name); - if (command == 0) + if (command == nullptr) { SG_LOG(SG_GENERAL, SG_WARN, "command not found: '" << name << "'"); return false; } + // use implicit root node if caller did not define one explicitly + if (root == nullptr) { + root = d->_rootNode.get(); + } + try { return (*command)(arg, root); @@ -122,13 +163,47 @@ SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg, SGPr bool SGCommandMgr::removeCommand(const std::string& name) { - command_map::iterator it = _commands.find(name); - if (it == _commands.end()) +#if !defined(NDEBUG) + assert(SGThread::current() == d->_mainThreadId); +#endif + auto it = d->_commands.find(name); + if (it == d->_commands.end()) return false; - + delete it->second; - _commands.erase(it); + d->_commands.erase(it); return true; } +void SGCommandMgr::queuedExecute(const std::string &name, const SGPropertyNode* arg) +{ + Invocation invoke = {name, new SGPropertyNode}; + copyProperties(arg, invoke.args); + SGGuard g(d->_mutex); + d->_queue.push_back(invoke); +} + +void SGCommandMgr::executedQueuedCommands() +{ +#if !defined(NDEBUG) + assert(SGThread::current() == d->_mainThreadId); +#endif + + // locked swap with the shared queue + Private::InvocactionVec q; + { + SGGuard g(d->_mutex); + d->_queue.swap(q); + } + + for (auto i : q) { + bool ok = execute(i.command, i.args); + if (!ok) { + SG_LOG(SG_GENERAL, SG_WARN, "queued execute of command " << i.command + << "failed"); + } + } +} + + // end of commands.cxx diff --git a/simgear/structure/commands.hxx b/simgear/structure/commands.hxx index 58d8c281..e8bf4e96 100644 --- a/simgear/structure/commands.hxx +++ b/simgear/structure/commands.hxx @@ -15,12 +15,13 @@ #include #include +#include #include // forward decls class SGPropertyNode; - + /** * Manage commands. * @@ -44,7 +45,7 @@ public: virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root) = 0; }; - + typedef bool (*command_t) (const SGPropertyNode * arg, SGPropertyNode *root); private: @@ -74,7 +75,7 @@ private: ObjPtr pObj_; MemFn pMemFn_; }; - + /** * Helper template functions. */ @@ -84,7 +85,7 @@ private: { return new MethodCommand(pObj, pMemFn ); } - + public: /** * Default constructor (sets instance to created item) @@ -98,6 +99,12 @@ public: static SGCommandMgr* instance(); + /** + * specify the root node to use if one is not explicitly provided when + * executing a command. + */ + void setImplicitRoot(SGPropertyNode* root); + /** * Register a new command with the manager. * @@ -109,15 +116,15 @@ public: */ void addCommand(const std::string& name, command_t f) { addCommandObject(name, new FunctionCommand(f)); } - + void addCommandObject (const std::string &name, Command* command); template void addCommand(const std::string& name, const OBJ& o, METHOD m) - { + { addCommandObject(name, make_functor(o,m)); } - + /** * Look up an existing command. * @@ -147,21 +154,37 @@ public: * @return true if the command is present and executes successfully, * false otherwise. */ - virtual bool execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const; + virtual bool execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root = nullptr) const; + + /** + * Queue a command for execution on the main thread / thread owning + * this command manager. (In practice in FlightGear, this means the same thing) + * The argument node will be copied immediately, so changes made after this call + * will not be reflected when the command is executed. + * + * Note there is no way to unqueue a command, or find out when the execution + * actually occurs, at present. Queued ommands are executed in the order submitted, + * however, which can be used to infer completion. + * + * Queued execution always uses the implicit root node. + */ + void queuedExecute(const std::string &name, const SGPropertyNode* arg); + + /** + * Dispatch queued command. FlightGear calls this once per frame + * on the main thread. + * + */ + void executedQueuedCommands(); /** * Remove a command registration */ bool removeCommand(const std::string& name); -protected: - - private: - - typedef std::map command_map; - command_map _commands; - + class Private; + std::unique_ptr d; }; #endif // __COMMANDS_HXX diff --git a/simgear/structure/event_mgr.cxx b/simgear/structure/event_mgr.cxx index 435b18f8..dead48b7 100644 --- a/simgear/structure/event_mgr.cxx +++ b/simgear/structure/event_mgr.cxx @@ -47,7 +47,7 @@ SGEventMgr::SGEventMgr() : _inited(false), _shutdown(false) { - +_name = "EventMgr"; } SGEventMgr::~SGEventMgr() @@ -86,10 +86,10 @@ void SGEventMgr::shutdown() void SGEventMgr::update(double delta_time_sec) { - _simQueue.update(delta_time_sec); - + _simQueue.update(delta_time_sec, _timerStats); + double rt = _rtProp ? _rtProp->getDoubleValue() : 0; - _rtQueue.update(rt); + _rtQueue.update(rt, _timerStats); } void SGEventMgr::removeTask(const std::string& name) @@ -169,19 +169,23 @@ void SGTimerQueue::clear() _table[i].timer = 0; } } - -void SGTimerQueue::update(double deltaSecs) +int maxTimerQueuePerItem_us = 30; +void SGTimerQueue::update(double deltaSecs, std::map &timingStats) { _now += deltaSecs; - while(_numEntries && nextTime() <= _now) { + + while (_numEntries && nextTime() <= _now) { SGTimer* t = remove(); - if(t->repeat) + if (t->repeat) insert(t, t->interval); // warning: this is not thread safe // but the entire timer queue isn't either + SGTimeStamp timeStamp; + timeStamp.stamp(); t->running = true; t->run(); t->running = false; + timingStats[t->name] += timeStamp.elapsedMSec() / 1000.0; if (!t->repeat) delete t; } diff --git a/simgear/structure/event_mgr.hxx b/simgear/structure/event_mgr.hxx index eaaec7f0..70d516fb 100644 --- a/simgear/structure/event_mgr.hxx +++ b/simgear/structure/event_mgr.hxx @@ -24,9 +24,8 @@ class SGTimerQueue { public: SGTimerQueue(int preSize=1); ~SGTimerQueue(); - void clear(); - void update(double deltaSecs); + void update(double deltaSecs, std::map &timingStats); double now() { return _now; } @@ -78,7 +77,6 @@ public: virtual void update(double delta_time_sec); virtual void unbind(); virtual void shutdown(); - void setRealtimeProperty(SGPropertyNode* node) { _rtProp = node; } /** diff --git a/simgear/structure/subsystem_mgr.cxx b/simgear/structure/subsystem_mgr.cxx index fc29e883..bd990546 100644 --- a/simgear/structure/subsystem_mgr.cxx +++ b/simgear/structure/subsystem_mgr.cxx @@ -45,8 +45,10 @@ using State = SGSubsystem::State; SGSubsystemTimingCb SGSubsystem::reportTimingCb = NULL; void* SGSubsystem::reportTimingUserData = NULL; +bool SGSubsystem::reportTimingStatsRequest = false; +int SGSubsystem::maxTimePerFrame_ms = 7; -SGSubsystem::SGSubsystem () +SGSubsystem::SGSubsystem () : _executionTime(0), _lastExecutionTime(0) { } @@ -202,16 +204,20 @@ std::string SGSubsystem::nameForState(State s) class SGSubsystemGroup::Member { private: - Member (const Member &member); + Member(const Member &member); public: - Member (); + Member(); ~Member (); void update (double delta_time_sec); void reportTiming(void) { if (reportTimingCb) reportTimingCb(reportTimingUserData, name, &timeStat); } - void updateExecutionTime(double time) { timeStat += time;} + void reportTimingStats(TimerStats *_lastValues) { + if (subsystem) + subsystem->reportTimingStats(_lastValues); + } + void updateExecutionTime(double time) { timeStat += time;} SampleStatistic timeStat; std::string name; SGSubsystemRef subsystem; @@ -220,15 +226,18 @@ public: bool collectTimeStats; int exceptionCount; int initTime; + + void mergeTimerStats(SGSubsystem::TimerStats &stats); }; -SGSubsystemGroup::SGSubsystemGroup () : - _fixedUpdateTime(-1.0), - _updateTimeRemainder(0.0), - _initPosition(-1) +SGSubsystemGroup::SGSubsystemGroup(const char *name) : + _fixedUpdateTime(-1.0), + _updateTimeRemainder(0.0), + _initPosition(-1) { + _name = name; } SGSubsystemGroup::~SGSubsystemGroup () @@ -377,21 +386,145 @@ SGSubsystemGroup::update (double delta_time_sec) const bool recordTime = (reportTimingCb != nullptr); SGTimeStamp timeStamp; + TimerStats lvTimerStats(_timerStats); + TimerStats overrunItems; + bool overrun = false; + + SGTimeStamp outerTimeStamp; + outerTimeStamp.stamp(); while (loopCount-- > 0) { for (auto member : _members) { - if (recordTime) - timeStamp = SGTimeStamp::now(); + timeStamp.stamp(); + if (member->subsystem->_timerStats.size()) { + member->subsystem->_lastTimerStats.clear(); + member->subsystem->_lastTimerStats.insert(member->subsystem->_timerStats.begin(), member->subsystem->_timerStats.end()); + } member->update(delta_time_sec); // indirect call + if (member->name.size()) + _timerStats[member->name] += timeStamp.elapsedMSec() / 1000.0; if (recordTime && reportTimingCb) { - timeStamp = SGTimeStamp::now() - timeStamp; - member->updateExecutionTime(timeStamp.toUSecs()); + member->updateExecutionTime(timeStamp.elapsedMSec()*1000); + if (timeStamp.elapsedMSec() > SGSubsystemMgr::maxTimePerFrame_ms) { + overrunItems[member->name] += timeStamp.elapsedMSec(); + overrun = true; + } } } } // of multiple update loop + _lastExecutionTime = _executionTime; + _executionTime += outerTimeStamp.elapsedMSec(); + if (overrun) { + for (auto overrunItem : overrunItems) { + SG_LOG(SG_EVENT, SG_ALERT, "Subsystem " + << overrunItem.first + << " total " + << std::setw(6) << std::fixed << std::setprecision(2) << std::right << _timerStats[overrunItem.first] + << "s overrun " + << std::setw(6) << std::fixed << std::setprecision(2) << std::right << overrunItem.second + << "ms"); + auto m = std::find_if(_members.begin(), _members.end(), [overrunItem](const Member* m) { + if (m->name == overrunItem.first) + return true; + return false; + }); + if (m != _members.end()) { + auto member = *m; + if (overrunItems[member->name]) { + TimerStats sst; + member->reportTimingStats(&_lastTimerStats); + //if (lvTimerStats[member->name] != _timerStats[member->name]) { + // SG_LOG(SG_EVENT, SG_ALERT, + // " +" << std::setw(6) << std::left << (_timerStats[member->name] - lvTimerStats[member->name]) + // << " total " << std::setw(6) << std::left << _timerStats[member->name] + // << " " << member->name + // ); + //} + } + } + } + } + + if (reportTimingStatsRequest) { + reportTimingStats(nullptr); + //for (auto member : _members) { + // member->mergeTimerStats(_timerStats); + // if (_timerStats.size()) { + // SG_LOG(SG_EVENT, SG_ALERT, "" << std::setw(6) << std::fixed << std::setprecision(2) << std::left << _timerStats[member->name] + // << ": " << member->name); + // for (auto item : _timerStats) { + // if (item.second > 0) + // SG_LOG(SG_EVENT, SG_ALERT, " " << std::setw(6) << std::left << item.second << " " << item.first); + // } + // } + //} + } + _lastTimerStats.clear(); + _lastTimerStats.insert(_timerStats.begin(), _timerStats.end()); + +} +void SGSubsystem::reportTimingStats(TimerStats *__lastValues) { + std::string _name = ""; + + bool reportDeltas = __lastValues != nullptr; + __lastValues = &_lastTimerStats; + std::ostringstream t; + if (reportDeltas) { + auto deltaT = _executionTime - _lastExecutionTime; + if (deltaT != 0) { + t << name() << "(+" << std::setprecision(2) << std::right << deltaT << "ms)."; + _name = t.str(); + } + } + else { + SG_LOG(SG_EVENT, SG_ALERT, "SubSystem: " << _name << " " << std::setw(6) << std::setprecision(4) << std::right << _executionTime / 1000.0 << "s"); + } + for (auto item : _timerStats) { + std::ostringstream output; + if (item.second > 0) { + if (reportDeltas) + { + auto delta = item.second - (*__lastValues)[item.first]; + if (delta != 0) { + output + << " +" << std::setw(6) << std::setprecision(4) << std::left << (delta * 1000.0) + << _name << item.first + << " total " << std::setw(6) << std::setprecision(4) << std::right << item.second << "s " + ; + } + } + else + output << " " << std::setw(6) << std::setprecision(4) << std::right << item.second << "s " << item.first; + if (output.str().size()) + SG_LOG(SG_EVENT, SG_ALERT, output.str()); + } + } } +void SGSubsystemGroup::reportTimingStats(TimerStats *_lastValues) { + SGSubsystem::reportTimingStats(_lastValues); + + std::string _name = name(); + if (!_name.size()) + _name = typeid(this).name(); + if (_lastValues) { + auto deltaT = _executionTime - _lastExecutionTime; + if (deltaT != 0) { + SG_LOG(SG_EVENT, SG_ALERT, + " +" << std::setw(6) << std::setprecision(4) << std::right << deltaT << "ms " + << name() ); + } + } + else + SG_LOG(SG_EVENT, SG_ALERT, "SubSystemGroup: " << name() << " " << std::setw(6) << std::setprecision(4) << std::right << _executionTime / 1000.0 << "s"); + for (auto member : _members) { + member->reportTimingStats(_lastValues); + } + _lastTimerStats.clear(); + _lastTimerStats.insert(_timerStats.begin(), _timerStats.end()); + +} void SGSubsystemGroup::reportTiming(void) { @@ -609,6 +742,7 @@ auto SGSubsystemGroup::get_member(const string &name, bool create) -> Member* Member* m = new Member; m->name = name; + _timerStats[name] = 0; _members.push_back(m); return _members.back(); } @@ -653,6 +787,11 @@ SGSubsystemGroup::Member::Member (const Member &) SGSubsystemGroup::Member::~Member () { } +void SGSubsystemGroup::Member::mergeTimerStats(SGSubsystem::TimerStats &stats) { + stats.insert(subsystem->_timerStats.begin(), subsystem->_timerStats.end()); + //for (auto ts : subsystem->_timerStats) + // ts.second = 0; +} void SGSubsystemGroup::Member::update (double delta_time_sec) @@ -666,10 +805,15 @@ SGSubsystemGroup::Member::update (double delta_time_sec) return; } + SGTimeStamp oTimer; try { - subsystem->update(elapsed_sec); - elapsed_sec = 0; - } catch (sg_exception& e) { + oTimer.stamp(); + subsystem->update(elapsed_sec); + subsystem->_lastExecutionTime = subsystem->_executionTime; + subsystem->_executionTime += oTimer.elapsedMSec(); + elapsed_sec = 0; + } + catch (sg_exception& e) { SG_LOG(SG_GENERAL, SG_ALERT, "caught exception processing subsystem:" << name << "\nmessage:" << e.getMessage()); @@ -693,7 +837,7 @@ namespace { } // end of anonymous namespace -SGSubsystemMgr::SGSubsystemMgr () : +SGSubsystemMgr::SGSubsystemMgr (const char *name) : _groups(MAX_GROUPS) { if (global_defaultSubsystemManager == nullptr) { @@ -710,7 +854,7 @@ SGSubsystemMgr::SGSubsystemMgr () : #endif for (int i = 0; i < MAX_GROUPS; i++) { - auto g = new SGSubsystemGroup; + auto g = new SGSubsystemGroup(name); g->set_manager(this); _groups[i].reset(g); } @@ -746,6 +890,7 @@ SGSubsystemMgr::init () _groups[i]->init(); } + SGSubsystem::InitStatus SGSubsystemMgr::incrementalInit() { @@ -802,9 +947,12 @@ SGSubsystemMgr::unbind () void SGSubsystemMgr::update (double delta_time_sec) { + SGTimeStamp timeStamp; + for (int i = 0; i < MAX_GROUPS; i++) { _groups[i]->update(delta_time_sec); } + reportTimingStatsRequest = false; } void diff --git a/simgear/structure/subsystem_mgr.hxx b/simgear/structure/subsystem_mgr.hxx index 95cd5a46..b991a9ef 100644 --- a/simgear/structure/subsystem_mgr.hxx +++ b/simgear/structure/subsystem_mgr.hxx @@ -130,7 +130,8 @@ typedef void (*SGSubsystemTimingCb)(void* userData, const std::string& name, Sam class SGSubsystem : public SGReferenced { public: - /** + using TimerStats = std::map; + /** * Default constructor. */ SGSubsystem (); @@ -270,6 +271,7 @@ public: */ void reportTiming(void); + virtual void reportTimingStats(TimerStats *_lastValues); /** * Place time stamps at strategic points in the execution of subsystems * update() member functions. Predominantly for debugging purposes. @@ -323,6 +325,20 @@ public: * debug helper, print a state as a string */ static std::string nameForState(State s); + + /** + * gets fine grained stats of time elapsed since last clear + * returns map of ident and time + */ + virtual const TimerStats &getTimerStats() { + return _timerStats; + } + + /** + * clear fine grained stats that are over the specified value. + */ + virtual void resetTimerStats(double val = 0) { } + protected: friend class SGSubsystemMgr; friend class SGSubsystemGroup; @@ -342,9 +358,16 @@ protected: static SGSubsystemTimingCb reportTimingCb; static void* reportTimingUserData; - + static bool reportTimingStatsRequest; + static int maxTimePerFrame_ms; + private: SGSubsystemGroup* _group = nullptr; +protected: + TimerStats _timerStats, _lastTimerStats; + double _executionTime; + double _lastExecutionTime; + }; typedef SGSharedPtr SGSubsystemRef; @@ -355,7 +378,7 @@ typedef SGSharedPtr SGSubsystemRef; class SGSubsystemGroup : public SGSubsystem { public: - SGSubsystemGroup (); + SGSubsystemGroup (const char *name); virtual ~SGSubsystemGroup (); void init() override; @@ -380,6 +403,7 @@ public: bool remove_subsystem (const std::string &name); virtual bool has_subsystem (const std::string &name) const; + void reportTimingStats(TimerStats *_lastValues) override; /** * Remove all subsystems. */ @@ -477,7 +501,7 @@ public: MAX_GROUPS }; - SGSubsystemMgr (); + SGSubsystemMgr (const char *name); virtual ~SGSubsystemMgr (); void init () override; @@ -510,7 +534,8 @@ public: SGSubsystem* get_subsystem(const std::string &name, const std::string& instanceName) const; void reportTiming(); - void setReportTimingCb(void* userData,SGSubsystemTimingCb cb) {reportTimingCb = cb;reportTimingUserData = userData;} + void setReportTimingCb(void* userData, SGSubsystemTimingCb cb) { reportTimingCb = cb; reportTimingUserData = userData; } + void setReportTimingStats(bool v) { reportTimingStatsRequest = v; } /** * @brief set the root property node for this subsystem manager diff --git a/simgear/structure/subsystem_test.cxx b/simgear/structure/subsystem_test.cxx index 219a3244..bdd0a6b5 100644 --- a/simgear/structure/subsystem_test.cxx +++ b/simgear/structure/subsystem_test.cxx @@ -90,7 +90,7 @@ class InstrumentGroup : public SGSubsystemGroup { public: static const char* subsystemName() { return "instruments"; } - + InstrumentGroup() : SGSubsystemGroup(InstrumentGroup::subsystemName()) {} virtual ~InstrumentGroup() { } @@ -164,7 +164,7 @@ SGSubsystemMgr::InstancedRegistrant registrant3(SGSubsystemMgr::PO void testRegistrationAndCreation() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST1"); auto anotherSub = manager->create(); SG_VERIFY(anotherSub); @@ -181,7 +181,7 @@ void testRegistrationAndCreation() void testAddGetRemove() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST1"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -228,7 +228,7 @@ void testAddGetRemove() void testSubGrouping() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST1"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -299,7 +299,7 @@ void testSubGrouping() void testIncrementalInit() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -347,7 +347,7 @@ void testEmptyGroup() // https://sourceforge.net/p/flightgear/codetickets/2043/ // when an empty group is inited, we skipped setting the state - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -371,7 +371,7 @@ void testEmptyGroup() void testSuspendResume() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -441,7 +441,7 @@ void testSuspendResume() void testPropertyRoot() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); SGPropertyNode_ptr props(new SGPropertyNode); manager->set_root_node(props); @@ -467,7 +467,7 @@ void testPropertyRoot() void testAddRemoveAfterInit() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); diff --git a/simgear/structure/test_commands.cxx b/simgear/structure/test_commands.cxx new file mode 100644 index 00000000..a107e8b0 --- /dev/null +++ b/simgear/structure/test_commands.cxx @@ -0,0 +1,128 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +using std::string; +using std::cout; +using std::cerr; +using std::endl; + +SGPropertyNode_ptr test_rootNode; + +bool commandAFunc(const SGPropertyNode* args, SGPropertyNode* root) +{ + SG_VERIFY(root == test_rootNode.get()); + root->setIntValue("propA", args->getIntValue("argA")); + return true; +} + +bool commandBFunc(const SGPropertyNode* args, SGPropertyNode* root) +{ + SG_VERIFY(root == test_rootNode.get()); + root->setIntValue("propB", args->getIntValue("argB")); + return args->getBoolValue("result"); +} + +void testBasicCommands() +{ + test_rootNode.reset(new SGPropertyNode); + SGCommandMgr::instance()->setImplicitRoot(test_rootNode.get()); + + SGCommandMgr::instance()->addCommand("cmd-a", commandAFunc); + SGCommandMgr::instance()->addCommand("cmd-b", commandBFunc); + + auto names = SGCommandMgr::instance()->getCommandNames(); + SG_CHECK_EQUAL(names.size(), 2); + SG_VERIFY(std::count(names.begin(), names.end(), "cmd-a")); + SG_VERIFY(std::count(names.begin(), names.end(), "cmd-b")); + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argA", 42); + bool ok = SGCommandMgr::instance()->execute("cmd-a", args); + SG_VERIFY(ok); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 42); + } + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argB", 99); + args->setBoolValue("result", true); + bool ok = SGCommandMgr::instance()->execute("cmd-b", args); + SG_VERIFY(ok); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propB"), 99); + } + + SGCommandMgr::instance()->removeCommand("cmd-a"); + names = SGCommandMgr::instance()->getCommandNames(); + SG_CHECK_EQUAL(names.size(), 1); + SG_VERIFY(std::count(names.begin(), names.end(), "cmd-b")); + + // check we can't execute a removed command + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argA", 66); + bool ok = SGCommandMgr::instance()->execute("cmd-a", args); + SG_VERIFY(!ok); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 42); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void testQueuedExec() +{ + // delete the previous instance, so next call re-creates + delete SGCommandMgr::instance(); + + test_rootNode.reset(new SGPropertyNode); + test_rootNode->setIntValue("propA", 71); + + SGCommandMgr::instance()->setImplicitRoot(test_rootNode.get()); + + SGCommandMgr::instance()->addCommand("cmd-a", commandAFunc); + SGCommandMgr::instance()->addCommand("cmd-b", commandBFunc); + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argA", 99); + SGCommandMgr::instance()->queuedExecute("cmd-a", args); + + // ensure args are cpatured during enqueue call + args->setIntValue("argA", 101); + } + + // not executed yet + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 71); + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argB", 1234); + args->setBoolValue("result", true); + SGCommandMgr::instance()->queuedExecute("cmd-b", args); + } + + SGCommandMgr::instance()->executedQueuedCommands(); + + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 99); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propB"), 1234); +} + +/////////////////////////////////////////////////////////////////////////////// + + +int main(int argc, char* argv[]) +{ + testBasicCommands(); + testQueuedExec(); + + cout << __FILE__ << ": All tests passed" << endl; + return EXIT_SUCCESS; +} diff --git a/version b/version index 5c8cec8e..b13705b7 100644 --- a/version +++ b/version @@ -1 +1 @@ -2018.4.0 +2019.2.0