Merge branch 'next' of https://git.code.sf.net/p/flightgear/simgear into next
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ foreach( mylibfolder
|
||||
tgdb
|
||||
util
|
||||
tsync
|
||||
viewer
|
||||
)
|
||||
|
||||
add_subdirectory(${mylibfolder})
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <osg/Version>
|
||||
#include <osg/PagedLOD>
|
||||
#include <osg/ProxyNode>
|
||||
#include <osgDB/ReadFile>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <osg/Version>
|
||||
#include <osg/CullFace>
|
||||
#include <osg/PagedLOD>
|
||||
#include <osg/MatrixTransform>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
17
simgear/scene/viewer/CMakeLists.txt
Normal file
17
simgear/scene/viewer/CMakeLists.txt
Normal 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}")
|
||||
216
simgear/scene/viewer/ClusteredForward.cxx
Normal file
216
simgear/scene/viewer/ClusteredForward.cxx
Normal 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
|
||||
40
simgear/scene/viewer/ClusteredForward.hxx
Normal file
40
simgear/scene/viewer/ClusteredForward.hxx
Normal 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 */
|
||||
312
simgear/scene/viewer/Compositor.cxx
Normal file
312
simgear/scene/viewer/Compositor.cxx
Normal 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
|
||||
138
simgear/scene/viewer/Compositor.hxx
Normal file
138
simgear/scene/viewer/Compositor.hxx
Normal 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 */
|
||||
228
simgear/scene/viewer/CompositorBuffer.cxx
Normal file
228
simgear/scene/viewer/CompositorBuffer.cxx
Normal 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
|
||||
46
simgear/scene/viewer/CompositorBuffer.hxx
Normal file
46
simgear/scene/viewer/CompositorBuffer.hxx
Normal 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 */
|
||||
694
simgear/scene/viewer/CompositorPass.cxx
Normal file
694
simgear/scene/viewer/CompositorPass.cxx
Normal 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
|
||||
133
simgear/scene/viewer/CompositorPass.hxx
Normal file
133
simgear/scene/viewer/CompositorPass.hxx
Normal 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 */
|
||||
62
simgear/scene/viewer/CompositorUtil.cxx
Normal file
62
simgear/scene/viewer/CompositorUtil.cxx
Normal 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
|
||||
72
simgear/scene/viewer/CompositorUtil.hxx
Normal file
72
simgear/scene/viewer/CompositorUtil.hxx
Normal 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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
128
simgear/structure/test_commands.cxx
Normal file
128
simgear/structure/test_commands.cxx
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user