This commit is contained in:
gallaert
2019-04-22 19:08:39 +01:00
53 changed files with 2961 additions and 271 deletions

View File

@@ -48,6 +48,8 @@
#include <simgear/debug/logstream.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGGuard.hxx>
#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<SGMutex> 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<Request*>(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;

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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("<html>See <a href=\"wibble\">Here</a></html>");
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;

View File

@@ -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();
}

View File

@@ -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)
{

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -24,6 +24,11 @@
#include <simgear/structure/map.hxx>
#include <boost/iterator/iterator_facade.hpp>
#if BOOST_VERSION >= 105600
#include <boost/core/enable_if.hpp>
#else
#include <boost/utility/enable_if.hpp>
#endif
namespace nasal
{

View File

@@ -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<typename Range>
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<typename range_result_iterator<Range>::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<SGVec4d>::printOn(std::ostream& stream) const
{
const SGVec4d vec
= static_cast<const SGRawValue<SGVec4d>*>(this)->getValue();
= static_cast<const SGRawValue<SGVec4d>*>(this)->getValue();
for (int i = 0; i < 4; ++i) {
stream << vec[i];
if (i < 3)

View File

@@ -55,13 +55,18 @@ namespace boost {
struct disable_if : public disable_if_c<Cond::value, T> {};
}
#else
# include <boost/utility.hpp>
# include <boost/type_traits/is_enum.hpp>
#if BOOST_VERSION >= 105600
#include <boost/core/enable_if.hpp>
#else
#include <boost/utility/enable_if.hpp>
#endif
# include <simgear/debug/logstream.hxx>
# include <simgear/math/SGMathFwd.hxx>
# include <simgear/math/sg_types.hxx>
#endif
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>

View File

@@ -9,6 +9,7 @@ foreach( mylibfolder
tgdb
util
tsync
viewer
)
add_subdirectory(${mylibfolder})

View File

@@ -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.

View File

@@ -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<std::string,osg::ref_ptr<osg::Texture2D> > _bufferList;
std::vector<osg::ref_ptr<EffectGeode> > _lightList;
bool _collectLights;
osg::ref_ptr<Effect> _effectOverride;
};
}
#endif

View File

@@ -23,6 +23,7 @@
#include "Pass.hxx"
#include <osg/Version>
#include <osg/PointSprite>
#include <osg/TexEnv>
#include <osg/TexEnvCombine>
@@ -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);

View File

@@ -24,6 +24,7 @@
#include "../../bvh/BVHPageRequest.hxx"
#include "../../bvh/BVHPager.hxx"
#include <osg/Version>
#include <osg/io_utils>
#include <osg/Camera>
#include <osg/Drawable>

View File

@@ -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<std::string> 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<std::string> 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<osg::Image> 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) {

View File

@@ -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)) {

View File

@@ -27,6 +27,7 @@
#include <boost/bind.hpp>
#include <osg/Version>
#include <osg/Geode>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
@@ -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());

View File

@@ -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<osg::Image> 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<osg::Texture2D> 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<Image> newImage = readRefImageFile(fullLiveryFile);
#endif
if (!newImage)
return 0;
CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
Texture2D* newTexture = static_cast<Texture2D*>(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)

View File

@@ -21,6 +21,7 @@
#include <boost/algorithm/string.hpp>
#include <osg/Version>
#include <osg/PagedLOD>
#include <osg/ProxyNode>
#include <osgDB/ReadFile>

View File

@@ -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;

View File

@@ -30,9 +30,11 @@
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <osg/AlphaFunc>
#include <osg/BlendFunc>
@@ -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

View File

@@ -33,18 +33,20 @@
#include <simgear/math/SGMath.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/props/propsfwd.hxx>
class SGStars : public SGReferenced {
osg::ref_ptr<osg::Vec4Array> 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 );

View File

@@ -25,6 +25,7 @@
#include <cassert>
#include <osg/Version>
#include <osg/CullFace>
#include <osg/PagedLOD>
#include <osg/MatrixTransform>

View File

@@ -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;
}
}

View File

@@ -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())
{

View File

@@ -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

View File

@@ -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}")

View File

@@ -0,0 +1,216 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/BufferIndexBinding>
#include <osg/BufferObject>
#include <osg/RenderInfo>
#include <osg/Texture3D>
#include <osg/TextureBuffer>
#include <osg/Version>
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<osg::Texture3D> 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<osg::TextureBuffer> 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<osg::UniformBufferObject> light_data_ubo =
new osg::UniformBufferObject;
_light_data->setBufferObject(light_data_ubo.get());
#if OSG_VERSION_LESS_THAN(3,6,0)
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
new osg::UniformBufferBinding(0, light_data_ubo.get(),
0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat));
#else
osg::ref_ptr<osg::UniformBufferBinding> 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<osg::Polytope> 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<GLushort *>
(_light_grid->data());
GLushort *index_data = reinterpret_cast<GLushort *>
(_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

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Camera>
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<osg::Image> _light_grid;
osg::ref_ptr<osg::Image> _light_indices;
osg::ref_ptr<osg::FloatArray> _light_data;
};
} // namespace compositor
} // namespace simgear
#endif /* SG_CLUSTERED_FORWARD_HXX */

View File

@@ -0,0 +1,312 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <algorithm>
#include <osgUtil/IntersectionVisitor>
#include <osgViewer/Renderer>
#include <osgViewer/Viewer>
#include <simgear/math/SGRect.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/scene/material/EffectCullVisitor.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
#include <simgear/structure/exception.hxx>
#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> 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<osg::Uniform> 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<double> 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<LineSegmentIntersector> 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<osg::Camera*>(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<osgViewer::Renderer*>(pass->camera->getRenderer());
for (int i = 0; i < 2; ++i) {
osgUtil::SceneView* sceneView = renderer->getSceneView(i);
osg::ref_ptr<osgUtil::CullVisitor::Identifier> 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<Pass> &p) {
return p->name == name;
});
if (it == _passes.end())
return 0;
return (*it);
}
} // namespace compositor
} // namespace simgear

View File

@@ -0,0 +1,138 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <unordered_map>
#include <vector>
// For osgUtil::LineSegmentIntersector::Intersections, which is a typedef.
#include <osgUtil/LineSegmentIntersector>
#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 <name>.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<osg::Uniform>,
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<std::string, osg::ref_ptr<Buffer>> BufferMap;
const BufferMap & getBufferMap() const { return _buffers; }
Buffer * getBuffer(const std::string &name) const;
typedef std::vector<osg::ref_ptr<Pass>> 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<osg::Viewport> _viewport;
std::string _name;
BufferMap _buffers;
PassList _passes;
BuiltinUniforms _uniforms;
};
} // namespace compositor
} // namespace simgear
#endif /* SG_COMPOSITOR_HXX */

View File

@@ -0,0 +1,228 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Texture1D>
#include <osg/Texture2D>
#include <osg/Texture2DArray>
#include <osg/Texture2DMultisample>
#include <osg/Texture3D>
#include <osg/TextureRectangle>
#include <osg/TextureCubeMap>
#include <osg/FrameBufferObject>
#include <simgear/props/props.hxx>
#include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include "Compositor.hxx"
#include "CompositorUtil.hxx"
namespace simgear {
namespace compositor {
struct BufferFormat {
GLint internal_format;
GLenum source_format;
GLenum source_type;
};
PropStringMap<BufferFormat> 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<osg::Texture::WrapMode> 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<osg::Texture::FilterMode> 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<osg::Texture::ShadowTextureMode> shadow_texture_mode_map = {
{"luminance", osg::Texture::LUMINANCE},
{"intensity", osg::Texture::INTENSITY},
{"alpha", osg::Texture::ALPHA}
};
PropStringMap<osg::Texture::ShadowCompareFunc> 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> 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<SGVec4d>());
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

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Texture>
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<osg::Texture> 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 */

View File

@@ -0,0 +1,694 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Depth>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/PolygonMode>
#include <osg/io_utils>
#include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/SGUpdateVisitor.hxx>
#include <simgear/structure/exception.hxx>
#include "ClusteredForward.hxx"
#include "Compositor.hxx"
#include "CompositorUtil.hxx"
namespace simgear {
namespace compositor {
PropStringMap<osg::Camera::BufferComponent> 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> 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<SGVec4d>());
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<SGVec4d>());
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> 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<osg::Geode> 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<EffectGeode> 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<osg::StateSet> 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<QuadPassBuilder> 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<osg::LightSource *>(&node);
if (light_source)
_light = light_source->getLight();
}
traverse(node);
}
osg::ref_ptr<osg::Light> getLight() const {
osg::ref_ptr<osg::Light> light_ref;
_light.lock(light_ref);
return light_ref;
}
protected:
std::string _name;
osg::observer_ptr<osg::Light> _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<osg::Light> 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<LightFinder> _light_finder;
float _near_m;
float _far_m;
osg::ref_ptr<osg::Uniform> _light_matrix_uniform;
osg::Vec2d _half_sm_size;
};
struct ShadowMapPassBuilder : public PassBuilder {
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
osg::ref_ptr<Pass> 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<ShadowMapPassBuilder> 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<osg::Vec3, osg::Vec3> 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> 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<ShadowMapUpdateCallback *>(
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<ScenePassBuilder> 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

View File

@@ -0,0 +1,133 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <unordered_map>
#include <osg/Camera>
#include <osg/View>
#include <simgear/scene/material/Effect.hxx>
#include <simgear/structure/Singleton.hxx>
#include <simgear/props/props.hxx>
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<osg::Camera> camera;
/** If null, there is no effect override for this pass. */
osg::ref_ptr<Effect> 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<PassUpdateCallback> 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<std::string, SGSharedPtr<PassBuilder>> PassBuilderMap;
struct PassBuilderMapSingleton : public Singleton<PassBuilderMapSingleton> {
PassBuilderMap _map;
};
template <typename T>
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 <typename T>
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 */

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <simgear/props/condition.hxx>
#include <simgear/props/props.hxx>
#include <simgear/scene/tgdb/userdata.hxx>
#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<SGCondition> 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

View File

@@ -0,0 +1,72 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <unordered_map>
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<class T>
using PropStringMap = std::unordered_map<std::string, T>;
template <class T>
bool findPropString(const std::string &str,
T &value,
const PropStringMap<T> &map)
{
auto itr = map.find(str);
if (itr == map.end())
return false;
value = itr->second;
return true;
}
template <class T>
bool findPropString(const SGPropertyNode *parent,
const std::string &child_name,
T &value,
const PropStringMap<T> &map)
{
const SGPropertyNode *child = parent->getNode(child_name);
if (child) {
if (findPropString<T>(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 */

View File

@@ -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

View File

@@ -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. */

View File

@@ -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;

View File

@@ -22,7 +22,11 @@
#include "SGSharedPtr.hxx"
#include <boost/type_traits/is_base_of.hpp>
#if BOOST_VERSION >= 105600
#include <boost/core/enable_if.hpp>
#else
#include <boost/utility/enable_if.hpp>
#endif
#ifdef _MSC_VER
# pragma warning(push)

View File

@@ -20,7 +20,7 @@
*/
#include <simgear_config.h>
#include "StateMachine.hxx"
#include <algorithm>
@@ -32,13 +32,13 @@
#include <simgear/props/condition.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/structure/exception.hxx>
namespace simgear
{
typedef std::vector<StateMachine::State_ptr> 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<SGCondition> _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<Transition_ptr> _transitions;
std::vector<Transition*> _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();
}

View File

@@ -4,9 +4,7 @@
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include <simgear_config.h>
#include <memory>
#include <cassert>
@@ -20,23 +18,46 @@
#include <simgear/threads/SGGuard.hxx>
#include <simgear/debug/logstream.hxx>
struct Invocation
{
std::string command;
SGPropertyNode_ptr args;
};
class SGCommandMgr::Private
{
public:
SGPropertyNode_ptr _rootNode;
using InvocactionVec = std::vector<Invocation>;
InvocactionVec _queue;
SGMutex _mutex;
using command_map = std::map<std::string,Command*> ;
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<SGMutex> 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<SGMutex> 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

View File

@@ -15,12 +15,13 @@
#include <string>
#include <map>
#include <memory>
#include <simgear/math/sg_types.hxx>
// 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<ObjPtr,MemFn>(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<class OBJ, typename METHOD>
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<std::string,Command*> command_map;
command_map _commands;
class Private;
std::unique_ptr<Private> d;
};
#endif // __COMMANDS_HXX

View File

@@ -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<std::string, double> &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;
}

View File

@@ -24,9 +24,8 @@ class SGTimerQueue {
public:
SGTimerQueue(int preSize=1);
~SGTimerQueue();
void clear();
void update(double deltaSecs);
void update(double deltaSecs, std::map<std::string, double> &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; }
/**

View File

@@ -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

View File

@@ -130,7 +130,8 @@ typedef void (*SGSubsystemTimingCb)(void* userData, const std::string& name, Sam
class SGSubsystem : public SGReferenced
{
public:
/**
using TimerStats = std::map<std::string, double>;
/**
* 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<SGSubsystem> SGSubsystemRef;
@@ -355,7 +378,7 @@ typedef SGSharedPtr<SGSubsystem> 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

View File

@@ -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<FakeRadioSub> registrant3(SGSubsystemMgr::PO
void testRegistrationAndCreation()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST1");
auto anotherSub = manager->create<AnotherSub>();
SG_VERIFY(anotherSub);
@@ -181,7 +181,7 @@ void testRegistrationAndCreation()
void testAddGetRemove()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST1");
auto d = new RecorderDelegate;
manager->addDelegate(d);
@@ -228,7 +228,7 @@ void testAddGetRemove()
void testSubGrouping()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST1");
auto d = new RecorderDelegate;
manager->addDelegate(d);
@@ -299,7 +299,7 @@ void testSubGrouping()
void testIncrementalInit()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> 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<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST");
auto d = new RecorderDelegate;
manager->addDelegate(d);
@@ -371,7 +371,7 @@ void testEmptyGroup()
void testSuspendResume()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST");
auto d = new RecorderDelegate;
manager->addDelegate(d);
@@ -441,7 +441,7 @@ void testSuspendResume()
void testPropertyRoot()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST");
SGPropertyNode_ptr props(new SGPropertyNode);
manager->set_root_node(props);
@@ -467,7 +467,7 @@ void testPropertyRoot()
void testAddRemoveAfterInit()
{
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr;
SGSharedPtr<SGSubsystemMgr> manager = new SGSubsystemMgr("TEST");
auto d = new RecorderDelegate;
manager->addDelegate(d);

View File

@@ -0,0 +1,128 @@
#include <simgear_config.h>
#include <cstdio>
#include <algorithm>
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/structure/commands.hxx>
#include <simgear/misc/test_macros.hxx>
#include <simgear/props/props.hxx>
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;
}

View File

@@ -1 +1 @@
2018.4.0
2019.2.0