diff --git a/3rdparty/udns/udns_init.c b/3rdparty/udns/udns_init.c index 493af589..c28d7c08 100644 --- a/3rdparty/udns/udns_init.c +++ b/3rdparty/udns/udns_init.c @@ -24,7 +24,7 @@ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) # include /* includes */ # include /* for dns server addresses etc */ #else @@ -53,7 +53,7 @@ static void dns_set_srch_internal(struct dns_ctx *ctx, char *srch) { dns_add_srch(ctx, srch); } -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) #ifndef NO_IPHLPAPI /* Apparently, some systems does not have proper headers for IPHLPAIP to work. @@ -217,7 +217,7 @@ int dns_init(struct dns_ctx *ctx, int do_open) { ctx = &dns_defctx; dns_reset(ctx); -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) if (dns_initns_iphlpapi(ctx) != 0) dns_initns_registry(ctx); /*XXX WINDOWS: probably good to get default domain and search list too... diff --git a/3rdparty/udns/udns_resolver.c b/3rdparty/udns/udns_resolver.c index b8f899a2..9f8b7503 100644 --- a/3rdparty/udns/udns_resolver.c +++ b/3rdparty/udns/udns_resolver.c @@ -24,7 +24,7 @@ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) # include /* includes */ # include /* needed for struct in6_addr */ #else @@ -392,7 +392,7 @@ dns_set_tmcbck(struct dns_ctx *ctx, dns_utm_fn *fn, void *data) { } static unsigned dns_nonrandom_32(void) { -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) FILETIME ft; GetSystemTimeAsFileTime(&ft); return ft.dwLowDateTime; @@ -551,7 +551,7 @@ int dns_open(struct dns_ctx *ctx) { ctx->dnsc_qstatus = DNS_E_TEMPFAIL; return -1; } -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) { unsigned long on = 1; if (ioctlsocket(sock, FIONBIO, &on) == SOCKET_ERROR) { closesocket(sock); @@ -991,7 +991,7 @@ again: /* receive the reply */ * or remote. On local errors, we should stop, while * remote errors should be ignored (for now anyway). */ -#ifdef WINDOWS +#if defined(_WINDOWS) || defined(WINDOWS) if (WSAGetLastError() == WSAEWOULDBLOCK) #else if (errno == EAGAIN) diff --git a/3rdparty/udns/udns_rr_a.c b/3rdparty/udns/udns_rr_a.c index 72cd2022..4492d049 100644 --- a/3rdparty/udns/udns_rr_a.c +++ b/3rdparty/udns/udns_rr_a.c @@ -27,7 +27,7 @@ #include #include #include -#ifndef WINDOWS +#if !defined(_WINDOWS) && !defined(WINDOWS) # include # include #endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a2e849f..17ea0b6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,7 +150,7 @@ endif() if (MSVC) - GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH) + GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} DIRECTORY) if (CMAKE_CL_64) SET(TEST_3RDPARTY_DIR "${PARENT_DIR}/3rdparty.x64") else (CMAKE_CL_64) @@ -198,7 +198,7 @@ if (MSVC AND MSVC_3RDPARTY_ROOT) # if this variable was not set by the user, set it to 3rdparty root's # parent dir, which is the normal location for people using our # windows-3rd-party repo - GET_FILENAME_COMPONENT(MSVC_ROOT_PARENT_DIR ${MSVC_3RDPARTY_ROOT} PATH) + get_filename_component(MSVC_ROOT_PARENT_DIR ${MSVC_3RDPARTY_ROOT} DIRECTORY) set(BOOST_INCLUDEDIR ${MSVC_ROOT_PARENT_DIR}) message(STATUS "BOOST_INCLUDEDIR is ${BOOST_INCLUDEDIR}") endif() diff --git a/CMakeModules/BoostTestTargets.cmake b/CMakeModules/BoostTestTargets.cmake index 3da31265..580bf9d2 100644 --- a/CMakeModules/BoostTestTargets.cmake +++ b/CMakeModules/BoostTestTargets.cmake @@ -80,7 +80,7 @@ if(Boost_FOUND AND NOT "${Boost_VERSION}0" LESS "1034000") set(_boostConfig "BoostTestTargetsDynamic.h") endif() endif() - get_filename_component(_moddir ${CMAKE_CURRENT_LIST_FILE} PATH) + get_filename_component(_moddir ${CMAKE_CURRENT_LIST_FILE} DIRECTORY) configure_file("${_moddir}/${_boostConfig}" "${CMAKE_CURRENT_BINARY_DIR}/BoostTestTargetConfig.h" COPYONLY) diff --git a/CMakeModules/CopyResourcesToBuildTree.cmake b/CMakeModules/CopyResourcesToBuildTree.cmake index 3512cc48..0593363d 100644 --- a/CMakeModules/CopyResourcesToBuildTree.cmake +++ b/CMakeModules/CopyResourcesToBuildTree.cmake @@ -30,12 +30,12 @@ function(copy_resources_to_build_tree _target) endif() get_target_property(_path ${_target} LOCATION) - get_filename_component(_path "${_path}" PATH) + get_filename_component(_path "${_path}" DIRECTORY) if(NOT MSVC AND NOT "${CMAKE_GENERATOR}" MATCHES "Makefiles") foreach(_config ${CMAKE_CONFIGURATION_TYPES}) get_target_property(_path${_config} ${_target} LOCATION_${_config}) - get_filename_component(_path${_config} "${_path${_config}}" PATH) + get_filename_component(_path${_config} "${_path${_config}}" DIRECTORY) add_custom_command(TARGET ${_target} POST_BUILD COMMAND diff --git a/simgear/CMakeLists.txt b/simgear/CMakeLists.txt index 6f8d2933..ca26c9d2 100644 --- a/simgear/CMakeLists.txt +++ b/simgear/CMakeLists.txt @@ -6,6 +6,7 @@ foreach( mylibfolder bvh debug embedded_resources + emesary ephemeris io magvar diff --git a/simgear/canvas/CanvasMgr.hxx b/simgear/canvas/CanvasMgr.hxx index 3255d4bc..e689a7f4 100644 --- a/simgear/canvas/CanvasMgr.hxx +++ b/simgear/canvas/CanvasMgr.hxx @@ -28,42 +28,39 @@ namespace simgear namespace canvas { - class CanvasMgr: - public PropertyBasedMgr - { - public: +class CanvasMgr : public PropertyBasedMgr +{ +public: + /** + * @param node Root node of branch used to control canvasses + */ + CanvasMgr(SGPropertyNode_ptr node); - /** - * @param node Root node of branch used to control canvasses - */ - CanvasMgr(SGPropertyNode_ptr node); + /** + * Create a new canvas + * + * @param name Name of the new canvas + */ + CanvasPtr createCanvas(const std::string& name = ""); - /** - * Create a new canvas - * - * @param name Name of the new canvas - */ - CanvasPtr createCanvas(const std::string& name = ""); + /** + * Get ::Canvas by index + * + * @param index Index of texture node in /canvas/by-index/ + */ + CanvasPtr getCanvas(size_t index) const; - /** - * Get ::Canvas by index - * - * @param index Index of texture node in /canvas/by-index/ - */ - CanvasPtr getCanvas(size_t index) const; + /** + * Get ::Canvas by name + * + * @param name Value of child node "name" in + * /canvas/by-index/texture[i]/name + */ + CanvasPtr getCanvas(const std::string& name) const; - /** - * Get ::Canvas by name - * - * @param name Value of child node "name" in - * /canvas/by-index/texture[i]/name - */ - CanvasPtr getCanvas(const std::string& name) const; - - protected: - - void elementCreated(PropertyBasedElementPtr element) override; - }; +protected: + void elementCreated(PropertyBasedElementPtr element) override; +}; } // namespace canvas } // namespace simgear diff --git a/simgear/debug/logstream.cxx b/simgear/debug/logstream.cxx index 93ba61b5..d5851001 100644 --- a/simgear/debug/logstream.cxx +++ b/simgear/debug/logstream.cxx @@ -425,8 +425,11 @@ public: return; } - m_startupLogging = on; - m_startupEntries.clear(); + { + SGGuard g(m_lock); + m_startupLogging = on; + m_startupEntries.clear(); + } } virtual void run() @@ -438,13 +441,14 @@ public: if ((entry.debugClass == SG_NONE) && !strcmp(entry.file, "done")) { return; } - - if (m_startupLogging) { - // save to the startup list for not-yet-added callbacks to - // pull down on startup - m_startupEntries.push_back(entry); + { + SGGuard g(m_lock); + if (m_startupLogging) { + // save to the startup list for not-yet-added callbacks to + // pull down on startup + m_startupEntries.push_back(entry); + } } - // submit to each installed callback in turn for (simgear::LogCallback* cb : m_callbacks) { (*cb)(entry.debugClass, entry.debugPriority, @@ -455,14 +459,16 @@ public: bool stop() { - SGGuard g(m_lock); - if (!m_isRunning) { - return false; - } + { + SGGuard g(m_lock); + if (!m_isRunning) { + return false; + } - // log a special marker value, which will cause the thread to wakeup, - // and then exit - log(SG_NONE, SG_ALERT, "done", -1, ""); + // log a special marker value, which will cause the thread to wakeup, + // and then exit + log(SG_NONE, SG_ALERT, "done", -1, ""); + } join(); m_isRunning = false; diff --git a/simgear/emesary/CMakeLists.txt b/simgear/emesary/CMakeLists.txt new file mode 100644 index 00000000..30ca3318 --- /dev/null +++ b/simgear/emesary/CMakeLists.txt @@ -0,0 +1,33 @@ + + +include (SimGearComponent) + +set(HEADERS + Emesary.hxx + INotification.hxx + IReceiver.hxx + ITransmitter.hxx + ReceiptStatus.hxx + Transmitter.hxx + notifications.hxx + ) + +set(SOURCES + Emesary.cxx + ) + +simgear_component(emesary emesary "${SOURCES}" "${HEADERS}") + + +if(ENABLE_TESTS) + +add_executable(test_emesary test_emesary.cxx) + +set_target_properties(test_emesary PROPERTIES + COMPILE_DEFINITIONS "SRC_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"" ) + +target_link_libraries(test_emesary ${TEST_LIBS}) +add_test(emesary ${EXECUTABLE_OUTPUT_PATH}/test_emesary) + + +endif(ENABLE_TESTS) diff --git a/simgear/emesary/Emesary.cxx b/simgear/emesary/Emesary.cxx new file mode 100644 index 00000000..eb2ecf56 --- /dev/null +++ b/simgear/emesary/Emesary.cxx @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------- +* +* Title : Emesary - class based inter-object communication +* +* File Type : Implementation File +* +* Description : Emesary main. +* : This only needs to instance the GlobalTransmitter as all of the +* : logic is in the header files (by design) +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002 +* +* Version : $Header: $ +* +* Copyright © 2002 Richard Harrison All Rights Reserved. +* +*---------------------------------------------------------------------------*/ + +#include "simgear/emesary/Emesary.hxx" + +namespace simgear +{ + namespace Emesary + { + + } +} diff --git a/simgear/emesary/Emesary.hxx b/simgear/emesary/Emesary.hxx new file mode 100644 index 00000000..fac9294c --- /dev/null +++ b/simgear/emesary/Emesary.hxx @@ -0,0 +1,48 @@ +#ifndef EMESARY_hxx +#define EMESARY_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - class based inter-object communication +* +* File Type : Implementation File +* +* Description : Provides generic inter-object communication. For an object to receive a message it +* : must first register with a Transmitter, such as GlobalTransmitter, and implement the +* : IReceiver interface. That's it. +* : To send a message use a Transmitter with an object. That's all there is to it. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +#include + +#include "ReceiptStatus.hxx" +#include "INotification.hxx" +#include "IReceiver.hxx" +#include "ITransmitter.hxx" +#include "Transmitter.hxx" +#include + +namespace simgear +{ + namespace Emesary + { + class GlobalTransmitter : public simgear::Singleton + { + public: + GlobalTransmitter() + { + } + virtual ~GlobalTransmitter() {} + }; + } +} +#endif diff --git a/simgear/emesary/INotification.hxx b/simgear/emesary/INotification.hxx new file mode 100644 index 00000000..0d6a517a --- /dev/null +++ b/simgear/emesary/INotification.hxx @@ -0,0 +1,54 @@ +#ifndef INOTIFICATION_hxx +#define INOTIFICATION_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Notification base class +* +* File Type : Implementation File +* +* Description : Base class (interface) for all Notifications. +* : This is also compatible with the usual implementation of how we +* : implement queued notifications. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +namespace simgear +{ + namespace Emesary + { + /// Interface (base class) for all notifications. + class INotification + { + public: + // text representation of notification type. must be unique across all notifications + virtual const char *GetType() = 0; + + /// Used to control the sending of notifications. If this returns false then the Transmitter + /// should not send this notification. + virtual bool IsReadyToSend() { return true; } + + /// Used to control the timeout. If this notification has timed out - then the processor is entitled + /// to true. + virtual bool IsTimedOut() { return false; } + + /// when this notification has completed the processing recipient must set this to true. + /// the processing recipient is responsible for follow on notifications. + /// a notification can remain as complete until the transmit queue decides to remove it from the queue. + /// there is no requirement that elements are removed immediately upon completion merely that once complete + /// the transmitter should not notify any more elements. + /// The current notification loop may be completed - following the usual convention unless Completed or Abort + /// is returned as the status. + virtual bool IsComplete() { return true; } + }; + } +} +#endif diff --git a/simgear/emesary/IReceiver.hxx b/simgear/emesary/IReceiver.hxx new file mode 100644 index 00000000..f21a8a7c --- /dev/null +++ b/simgear/emesary/IReceiver.hxx @@ -0,0 +1,47 @@ +#ifndef IRECEIVER_hxx +#define IRECEIVER_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Receiver base class +* +* File Type : Implementation File +* +* Description : Base class for all recipients. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +namespace simgear +{ + namespace Emesary + { + + /// Interface (base class) for a recipeint. + class IReceiver + { + public: + /// Receive notification - must be implemented + virtual ReceiptStatus Receive(INotification& message) = 0; + + /// Called when registered at a transmitter + virtual void OnRegisteredAtTransmitter(class Transmitter *p) + { + } + + /// Called when de-registered at a transmitter + virtual void OnDeRegisteredAtTransmitter(class Transmitter *p) + { + } + }; + + } +} +#endif \ No newline at end of file diff --git a/simgear/emesary/ITransmitter.hxx b/simgear/emesary/ITransmitter.hxx new file mode 100644 index 00000000..cd4f750c --- /dev/null +++ b/simgear/emesary/ITransmitter.hxx @@ -0,0 +1,52 @@ +#ifndef ITRANSMITTER_hxx +#define ITRANSMITTER_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Transmitter base class +* +* File Type : Implementation File +* +* Description : Base class for all transmitters. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ + +namespace simgear +{ + namespace Emesary + { + /// Interface (base clasee) for a transmitter. + /// Transmits Message derived objects. Each instance of this class provides a + /// event/databus to which any number of receivers can attach to. + class ITransmitter + { + public: + // Registers a recipient to receive message from this transmitter + virtual void Register(IReceiver& R) = 0; + // Removes a recipient from from this transmitter + virtual void DeRegister(IReceiver& R) = 0; + + + //Notify all registered recipients. Stop when receipt status of abort or finished are received. + //The receipt status from this method will be + // - OK > message handled + // - Fail > message not handled. A status of Abort from a recipient will result in our status + // being fail as Abort means that the message was not and cannot be handled, and + // allows for usages such as access controls. + virtual ReceiptStatus NotifyAll(INotification& M) = 0; + + /// number of recipients + virtual int Count() = 0; + }; + } +} +#endif diff --git a/simgear/emesary/ReceiptStatus.hxx b/simgear/emesary/ReceiptStatus.hxx new file mode 100644 index 00000000..4620e00e --- /dev/null +++ b/simgear/emesary/ReceiptStatus.hxx @@ -0,0 +1,54 @@ +#ifndef RECEIPTSTATUS_hxx +#define RECEIPTSTATUS_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Transmitter base class +* +* File Type : Implementation File +* +* Description : Defines the receipt status that can be returned from +* : a receive method. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +namespace simgear +{ + namespace Emesary + { + enum ReceiptStatus + { + /// Processing completed successfully + ReceiptStatusOK = 0, + + /// Individual item failure + ReceiptStatusFail = 1, + + /// Fatal error; stop processing any further recipieints of this message. Implicitly fail + ReceiptStatusAbort = 2, + + /// Definitive completion - do not send message to any further recipieints + ReceiptStatusFinished = 3, + + /// Return value when method doesn't process a message. + ReceiptStatusNotProcessed = 4, + + /// Message has been sent but the return status cannot be determined as it has not been processed by the recipient. + /// e.g. a queue or outgoing bridge + ReceiptStatusPending = 5, + + /// Message has been definitively handled but the return value cannot be determined. The message will not be sent any further + /// e.g. a point to point forwarding bridge + ReceiptStatusPendingFinished = 6, + }; + } +} +#endif diff --git a/simgear/emesary/Transmitter.hxx b/simgear/emesary/Transmitter.hxx new file mode 100644 index 00000000..339a5488 --- /dev/null +++ b/simgear/emesary/Transmitter.hxx @@ -0,0 +1,203 @@ +#ifndef TRANSMITTER_hxx +#define TRANSMITTER_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Transmitter base class +* +* File Type : Implementation File +* +* Description : Defines the receipt status that can be returned from +* : a receive method. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace simgear +{ + namespace Emesary + { + // Implementation of a ITransmitter + class Transmitter : public ITransmitter + { + protected: + typedef std::list RecipientList; + RecipientList recipient_list; + RecipientList deleted_recipients; + int CurrentRecipientIndex = 0; + SGMutex _lock; + std::atomic receiveDepth; + std::atomic sentMessageCount; + + void UnlockList() + { + _lock.unlock(); + } + void LockList() + { + _lock.lock(); + } + public: + Transmitter() : receiveDepth(0), sentMessageCount(0) + { + } + + virtual ~Transmitter() + { + } + + // Registers an object to receive messsages from this transmitter. + // This object is added to the top of the list of objects to be notified. This is deliberate as + // the sequence of registration and message receipt can influence the way messages are processing + // when ReceiptStatus of Abort or Finished are encountered. So it was a deliberate decision that the + // most recently registered recipients should process the messages/events first. + virtual void Register(IReceiver& r) + { + LockList(); + recipient_list.push_back(&r); + r.OnRegisteredAtTransmitter(this); + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end()) + deleted_recipients.remove(&r); + + UnlockList(); + } + + // Removes an object from receving message from this transmitter + virtual void DeRegister(IReceiver& R) + { + LockList(); + //printf("Remove %x\n", &R); + if (recipient_list.size()) + { + if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end()) + { + recipient_list.remove(&R); + R.OnDeRegisteredAtTransmitter(this); + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &R) == deleted_recipients.end()) + deleted_recipients.push_back(&R); + } + } + UnlockList(); + } + + // Notify all registered recipients. Stop when receipt status of abort or finished are received. + // The receipt status from this method will be + // - OK > message handled + // - Fail > message not handled. A status of Abort from a recipient will result in our status + // being fail as Abort means that the message was not and cannot be handled, and + // allows for usages such as access controls. + virtual ReceiptStatus NotifyAll(INotification& M) + { + ReceiptStatus return_status = ReceiptStatusNotProcessed; + + sentMessageCount++; + try + { + LockList(); + if (receiveDepth == 0) + deleted_recipients.clear(); + receiveDepth++; + std::vector temp(recipient_list.size()); + int idx = 0; + for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++) + { + temp[idx++] = *i; + } + UnlockList(); + int tempSize = temp.size(); + for (int index = 0; index < tempSize; index++) + { + IReceiver* R = temp[index]; + LockList(); + if (deleted_recipients.size()) + { + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end()) + { + UnlockList(); + continue; + } + } + UnlockList(); + if (R) + { + ReceiptStatus rstat = R->Receive(M); + switch (rstat) + { + case ReceiptStatusFail: + return_status = ReceiptStatusFail; + break; + case ReceiptStatusPending: + return_status = ReceiptStatusPending; + break; + case ReceiptStatusPendingFinished: + return rstat; + + case ReceiptStatusNotProcessed: + break; + case ReceiptStatusOK: + if (return_status == ReceiptStatusNotProcessed) + return_status = rstat; + break; + + case ReceiptStatusAbort: + return ReceiptStatusAbort; + + case ReceiptStatusFinished: + return ReceiptStatusOK; + } + } + + } + } + catch (...) + { + throw; + // return_status = ReceiptStatusAbort; + } + receiveDepth--; + return return_status; + } + + // number of currently registered recipients + virtual int Count() + { + LockList(); + return recipient_list.size(); + UnlockList(); + } + + // number of sent messages. + int SentMessageCount() + { + return sentMessageCount; + } + + // ascertain if a receipt status can be interpreted as failure. + static bool Failed(ReceiptStatus receiptStatus) + { + // + // failed is either Fail or Abort. + // NotProcessed isn't a failure because it hasn't been processed. + return receiptStatus == ReceiptStatusFail + || receiptStatus == ReceiptStatusAbort; + } + }; + } +} +#endif diff --git a/simgear/emesary/notifications.hxx b/simgear/emesary/notifications.hxx new file mode 100644 index 00000000..0c98e3c6 --- /dev/null +++ b/simgear/emesary/notifications.hxx @@ -0,0 +1,68 @@ +#ifndef NOTIFICATIONS_hxx +#define NOTIFICATIONS_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - class based inter-object communication +* +* File Type : Implementation File +* +* Description : simgear notifications +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017 +* +* Version : $Header: $ +* +* Copyright � 2002 - 2017 Richard Harrison All Rights Reserved. +* +*---------------------------------------------------------------------------*/ + +#include "INotification.hxx" + +namespace simgear +{ + namespace Notifications + { + class MainLoopNotification : public simgear::Emesary::INotification + { + public: + enum Type { Started, Stopped, Begin, End }; + MainLoopNotification(Type v) : _type(v) {} + + virtual Type GetValue() { return _type; } + virtual const char *GetType() { return "MainLoop"; } + + protected: + Type _type; + }; + + class NasalGarbageCollectionConfigurationNotification : public simgear::Emesary::INotification + { + public: + NasalGarbageCollectionConfigurationNotification(bool canWait, bool active) : CanWait(canWait), Active(active) {} + + virtual bool GetCanWait() { return CanWait; } + virtual bool GetActive() { return Active; } + virtual const char *GetType() { return "NasalGarbageCollectionConfiguration"; } + virtual bool SetWait(bool wait) { + if (wait == CanWait) + return false; + CanWait = wait; + return true; + } + virtual bool SetActive(bool active) { + if (active == Active) + return false; + Active = active; + return true; + } + public: + bool CanWait; + bool Active; + }; + } +} +#endif diff --git a/simgear/emesary/test_emesary.cxx b/simgear/emesary/test_emesary.cxx new file mode 100644 index 00000000..852fa55b --- /dev/null +++ b/simgear/emesary/test_emesary.cxx @@ -0,0 +1,126 @@ +//////////////////////////////////////////////////////////////////////// +// Test harness for Emesary. +//////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include + +using std::cout; +using std::cerr; +using std::endl; + +std::atomic nthread {0}; +std::atomic noperations {0}; +const int MaxIterations = 9999; + +class TestThreadNotification : public simgear::Emesary::INotification +{ +protected: + const char *baseValue; +public: + TestThreadNotification(const char *v) : baseValue(v) {} + + virtual const char* GetType () { return baseValue; } +}; + +class TestThreadRecipient : public simgear::Emesary::IReceiver +{ +public: + TestThreadRecipient() : receiveCount(0) + { + + } + + std::atomic receiveCount; + virtual simgear::Emesary::ReceiptStatus Receive(simgear::Emesary::INotification &n) + { + if (n.GetType() == (const char*)this) + { + TestThreadNotification *tn = dynamic_cast(&n); + receiveCount++; + TestThreadNotification onwardNotification("AL"); + simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(onwardNotification); + return simgear::Emesary::ReceiptStatusOK; + } + return simgear::Emesary::ReceiptStatusOK; + } +}; + +class EmesaryTestThread : public SGThread +{ +protected: + virtual void run() { + int threadId = nthread.fetch_add(1); + + //System.Threading.Interlocked.Increment(ref nthread); + //var rng = new Random(); + TestThreadRecipient r; + char temp[100]; + sprintf(temp, "Notif %d", threadId); + printf("starting thread %s\n", temp); + TestThreadNotification tn((const char*)&r); + for (int i = 0; i < MaxIterations; i++) + { + simgear::Emesary::GlobalTransmitter::instance()->Register(r); + simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(tn); + simgear::Emesary::GlobalTransmitter::instance()->DeRegister(r); + //System.Threading.Thread.Sleep(rng.Next(MaxSleep)); + noperations++; + } + printf("%s invocations %d\n", temp, (int)r.receiveCount); + printf("finish thread %s\n", temp); + } +}; + +class EmesaryTest +{ +public: + + void Emesary_MultiThreadTransmitterTest() + { + int num_threads = 12; + std::list threads; + + for (int i = 0; i < num_threads; i++) + { + EmesaryTestThread *thread = new EmesaryTestThread(); + threads.push_back(thread); + thread->start(); + } + for (std::list::iterator i = threads.begin(); i != threads.end(); i++) + { + (*i)->join(); + } + } +}; + +void testEmesaryThreaded() +{ + TestThreadRecipient r; + TestThreadNotification tn((const char*)&r); + simgear::Emesary::GlobalTransmitter::instance()->Register(r); + for (int i = 0; i < MaxIterations*MaxIterations; i++) + { + simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(tn); + //System.Threading.Thread.Sleep(rng.Next(MaxSleep)); + noperations++; + } + simgear::Emesary::GlobalTransmitter::instance()->DeRegister(r); + printf("invocations %d\n", simgear::Emesary::GlobalTransmitter::instance()->SentMessageCount()); + + EmesaryTest t; + t.Emesary_MultiThreadTransmitterTest(); +} + + +int main(int ac, char ** av) +{ + testEmesaryThreaded(); + + std::cout << "all tests passed" << std::endl; + return 0; +} diff --git a/simgear/misc/CMakeLists.txt b/simgear/misc/CMakeLists.txt index 2931a3b3..33cc4a52 100644 --- a/simgear/misc/CMakeLists.txt +++ b/simgear/misc/CMakeLists.txt @@ -19,6 +19,7 @@ set(HEADERS tabbed_values.hxx texcoord.hxx test_macros.hxx + lru_cache.hxx ) set(SOURCES diff --git a/simgear/misc/interpolator.hxx b/simgear/misc/interpolator.hxx index e0721c6c..df530ee8 100644 --- a/simgear/misc/interpolator.hxx +++ b/simgear/misc/interpolator.hxx @@ -30,11 +30,16 @@ // code can register another one immediately without worrying about // timer aliasing. -class SGInterpolator : public SGSubsystem { +class SGInterpolator : public SGSubsystem +{ public: SGInterpolator() { _list = 0; } - virtual void init() {} - virtual void update(double delta_time_sec); + + // Subsystem API. + void update(double delta_time_sec) override; + + // Subsystem identification. + static const char* staticSubsystemClassId() { return "interpolator"; } // Simple method that interpolates a double property value from // its current value (default of zero) to the specified target diff --git a/simgear/misc/lru_cache.hxx b/simgear/misc/lru_cache.hxx new file mode 100644 index 00000000..92f85b57 --- /dev/null +++ b/simgear/misc/lru_cache.hxx @@ -0,0 +1,173 @@ +///@file +/// Compare lists and get differences +//---------------------------------------------------------------------------// +// Copyright (c) 2013 Kyle Lutz +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// See http://boostorg.github.com/compute for more information. +//---------------------------------------------------------------------------// +/// +// Changes Copyright (C) 2019 Richard Harrison (rjh@zaretto.com) +// +// As the boost licence is lax and permissive see +// (https://www.gnu.org/licenses/license-list.en.html#boost) +// any changes to this module are covered under the GPL +// +// 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 LRU_CACHE_HXX_ +#define LRU_CACHE_HXX_ + +#include +#include +#include +#include + +#include +#include + +namespace simgear +{ + // a cache which evicts the least recently used item when it is full + template + class lru_cache + { + public: + SGMutex _mutex; + + typedef Key key_type; + typedef Value value_type; + typedef std::list list_type; + typedef std::map< + key_type, + std::pair + > map_type; + + lru_cache(size_t capacity) + : m_capacity(capacity) + { + } + + ~lru_cache() + { + } + + + size_t size() const + { + return m_map.size(); + } + + size_t capacity() const + { + return m_capacity; + } + + bool empty() const + { + return m_map.empty(); + } + + bool contains(const key_type &key) + { + SGGuard scopeLock(_mutex); + return m_map.find(key) != m_map.end(); + } + + void insert(const key_type &key, const value_type &value) + { + SGGuard scopeLock(_mutex); + typename map_type::iterator i = m_map.find(key); + if (i == m_map.end()) { + // insert item into the cache, but first check if it is full + if (size() >= m_capacity) { + // cache is full, evict the least recently used item + evict(); + } + + // insert the new item + m_list.push_front(key); + m_map[key] = std::make_pair(value, m_list.begin()); + } + } + boost::optional findValue(const std::string &requiredValue) + { + SGGuard scopeLock(_mutex); + for (typename map_type::iterator it = m_map.begin(); it != m_map.end(); ++it) + if (it->second.first == requiredValue) + return it->first; + return boost::none; + } + boost::optional get(const key_type &key) + { + SGGuard scopeLock(_mutex); + // lookup value in the cache + typename map_type::iterator i = m_map.find(key); + if (i == m_map.end()) { + // value not in cache + return boost::none; + } + + // return the value, but first update its place in the most + // recently used list + typename list_type::iterator j = i->second.second; + if (j != m_list.begin()) { + // move item to the front of the most recently used list + m_list.erase(j); + m_list.push_front(key); + + // update iterator in map + j = m_list.begin(); + const value_type &value = i->second.first; + m_map[key] = std::make_pair(value, j); + + // return the value + return value; + } + else { + // the item is already at the front of the most recently + // used list so just return it + return i->second.first; + } + } + + void clear() + { + SGGuard scopeLock(_mutex); + m_map.clear(); + m_list.clear(); + } + + private: + void evict() + { + SGGuard scopeLock(_mutex); + // evict item from the end of most recently used list + typename list_type::iterator i = --m_list.end(); + m_map.erase(*i); + m_list.erase(i); + } + + private: + map_type m_map; + list_type m_list; + size_t m_capacity; + }; +} // namespace simgear + +#endif /* SG_LISTDIFF_HXX_ */ diff --git a/simgear/nasal/code.c b/simgear/nasal/code.c index faf534ec..0783b38b 100644 --- a/simgear/nasal/code.c +++ b/simgear/nasal/code.c @@ -157,7 +157,7 @@ static void initContext(naContext c) c->error[0] = 0; c->userData = 0; } - +#define BASE_SIZE 256000 static void initGlobals() { int i; @@ -168,10 +168,10 @@ static void initGlobals() globals->sem = naNewSem(); globals->lock = naNewLock(); - globals->allocCount = 256; // reasonable starting value + globals->allocCount = BASE_SIZE; // reasonable starting value for(i=0; ipools[i]), i); - globals->deadsz = 256; + globals->deadsz = BASE_SIZE; globals->ndead = 0; globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); @@ -833,9 +833,13 @@ naRef naGetSourceFile(naContext ctx, int frame) { naRef f; frame = findFrame(ctx, &ctx, frame); - f = ctx->fStack[frame].func; - f = PTR(f).func->code; - return PTR(f).code->srcFile; + if (frame >= 0) { + f = ctx->fStack[frame].func; + f = PTR(f).func->code; + if (!IS_NIL(f) && PTR(f).code) + return PTR(f).code->srcFile; + } + return naNil(); } char* naGetError(naContext ctx) diff --git a/simgear/nasal/cppbind/CMakeLists.txt b/simgear/nasal/cppbind/CMakeLists.txt index 08f09937..ca2475c7 100644 --- a/simgear/nasal/cppbind/CMakeLists.txt +++ b/simgear/nasal/cppbind/CMakeLists.txt @@ -5,6 +5,7 @@ set(HEADERS Ghost.hxx NasalCallContext.hxx NasalContext.hxx + NasalEmesaryInterface.hxx NasalHash.hxx NasalMe.hxx NasalMethodHolder.hxx diff --git a/simgear/nasal/cppbind/NasalEmesaryInterface.hxx b/simgear/nasal/cppbind/NasalEmesaryInterface.hxx new file mode 100644 index 00000000..b6d01ca1 --- /dev/null +++ b/simgear/nasal/cppbind/NasalEmesaryInterface.hxx @@ -0,0 +1,123 @@ +#ifndef NASALEMESARYINTERFACE_INCLUDED +#define NASALEMESARYINTERFACE_INCLUDED 1 +// Nasal Emesary receipient interface. +// +// Copyright (C) 2019 Richard Harrison rjh@zaretto.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 +#include + +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include + + +namespace nasal +{ + extern"C" { + extern int GCglobalAlloc(); + extern int naGarbageCollect(); + // these are used by the detailed debug in the Nasal GC. + SGTimeStamp global_timestamp; + void global_stamp() { + global_timestamp.stamp(); + } + extern int global_elapsedUSec() + { + return global_timestamp.elapsedUSec(); + } + } + + class ThreadedGarbageCollector : public SGExclusiveThread { + public: + ThreadedGarbageCollector() : SGExclusiveThread() {} + virtual ~ThreadedGarbageCollector() {} + + virtual int process(){ + return naGarbageCollect(); + } + }; + + class NasalMainLoopRecipient : public simgear::Emesary::IReceiver { + public: + NasalMainLoopRecipient() : receiveCount(0) { + simgear::Emesary::GlobalTransmitter::instance()->Register(*this); + SG_LOG(SG_NASAL, SG_INFO, "NasalMainLoopRecipient created"); + } + virtual ~NasalMainLoopRecipient() { + simgear::Emesary::GlobalTransmitter::instance()->DeRegister(*this); + } + + std::atomic receiveCount; + virtual simgear::Emesary::ReceiptStatus Receive(simgear::Emesary::INotification &n) + { + + simgear::Notifications::MainLoopNotification *mln = dynamic_cast(&n); + + if (mln) { + switch (mln->GetValue()) { + case simgear::Notifications::MainLoopNotification::Type::Begin: + if (gct.is_running()) { + if (Active && CanWait) + gct.awaitCompletion(); + else + gct.clearAwaitCompletionTime(); + } + break; + case simgear::Notifications::MainLoopNotification::Type::End: + if (Active) { + if (gct.is_running()) + gct.release(); + } + break; + case simgear::Notifications::MainLoopNotification::Type::Started: + gct.ensure_running(); + break; + case simgear::Notifications::MainLoopNotification::Type::Stopped: + gct.terminate(); + break; + } + return simgear::Emesary::ReceiptStatusOK; + } + + auto *gccn = dynamic_cast(&n); + if (gccn) { + CanWait = gccn->GetCanWait(); + Active = gccn->GetActive(); + return simgear::Emesary::ReceiptStatusOK; + } + return simgear::Emesary::ReceiptStatusNotProcessed; + } + protected: + bool CanWait; + bool Active; + ThreadedGarbageCollector gct; + }; + +} // namespace nasal +#endif diff --git a/simgear/nasal/cppbind/detail/to_nasal_helper.cxx b/simgear/nasal/cppbind/detail/to_nasal_helper.cxx index 752a7d5a..26d3363f 100644 --- a/simgear/nasal/cppbind/detail/to_nasal_helper.cxx +++ b/simgear/nasal/cppbind/detail/to_nasal_helper.cxx @@ -19,6 +19,7 @@ #include "to_nasal_helper.hxx" #include #include +#include #include #include @@ -27,6 +28,19 @@ namespace nasal { + // create single instance of the main loop recipient for Nasal - this will self register at the + // global transmitter - and that's all that is needed to link up the background GC to the main + // loop in FG that will send out the MainLoop notifications. + //class NasalMainLoopRecipientSingleton : public simgear::Singleton + //{ + //public: + // NasalMainLoopRecipientSingleton() + // { + // } + // virtual ~NasalMainLoopRecipientSingleton() {} + //}; + NasalMainLoopRecipient mrl; + //---------------------------------------------------------------------------- naRef to_nasal_helper(naContext c, const std::string& str) { diff --git a/simgear/nasal/gc.c b/simgear/nasal/gc.c index 5ac9c43c..67a0100b 100644 --- a/simgear/nasal/gc.c +++ b/simgear/nasal/gc.c @@ -1,7 +1,6 @@ #include "nasal.h" #include "data.h" #include "code.h" - #define MIN_BLOCK_SIZE 32 static void reap(struct naPool* p); @@ -12,14 +11,17 @@ struct Block { char* block; struct Block* next; }; - // Must be called with the giant exclusive lock! -static void freeDead() +extern void global_stamp(); +extern int global_elapsedUSec(); + +static int freeDead() { int i; for(i=0; indead; i++) naFree(globals->deadBlocks[i]); globals->ndead = 0; + return i; } static void marktemps(struct Context* c) @@ -31,50 +33,127 @@ static void marktemps(struct Context* c) mark(r); } } - +//#define GC_DETAIL_DEBUG +static int __elements_visited = 0; +static int gc_busy=0; // Must be called with the big lock! static void garbageCollect() { + if (gc_busy) + return; + gc_busy = 1; int i; struct Context* c; globals->allocCount = 0; c = globals->allContexts; - while(c) { - for(i=0; iallContexts; + while (c) { +#if GC_DETAIL_DEBUG + ctxc++; +#endif + for (i = 0; i < NUM_NASAL_TYPES; i++) c->nfree[i] = 0; - for(i=0; i < c->fTop; i++) { + for (i = 0; i < c->fTop; i++) { mark(c->fStack[i].func); mark(c->fStack[i].locals); } - for(i=0; i < c->opTop; i++) + for (i = 0; i < c->opTop; i++) mark(c->opStack[i]); mark(c->dieArg); marktemps(c); c = c->nextAll; } +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + printf("--> garbageCollect(#e%-5d): %-4d ", eel, et); +#endif mark(globals->save); +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + printf("s(%5d) %-5d ", eel, et); +#endif + mark(globals->save_hash); +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + printf("h(%5d) %-5d ", eel, et); +#endif + + mark(globals->symbols); +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf("sy(%5d) %-4d ", eel, et); +#endif + mark(globals->meRef); +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf("me(%5d) %-5d ", eel, et); +#endif + mark(globals->argRef); +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf("ar(%5d) %-5d ", eel, et); +#endif + mark(globals->parentsRef); - +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; +#endif + //printf(" ev[%3d] %-5d", eel, et); // Finally collect all the freed objects - for(i=0; ipools[i])); - + } +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + printf(" >> reap %-5d", et); +#endif // Make enough space for the dead blocks we need to free during // execution. This works out to 1 spot for every 2 live objects, // which should be limit the number of bottleneck operations // without imposing an undue burden of extra "freeable" memory. if(globals->deadsz < globals->allocCount) { globals->deadsz = globals->allocCount; - if(globals->deadsz < 256) globals->deadsz = 256; + if(globals->deadsz < 256000) globals->deadsz = 256000; naFree(globals->deadBlocks); globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); } globals->needGC = 0; +#if GC_DETAIL_DEBUG + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + printf(">> %-5d ", et); +#endif + gc_busy = 0; } void naModLock() @@ -104,6 +183,7 @@ void naModUnlock() // you think about it). static void bottleneck() { + global_stamp(); struct Globals* g = globals; g->bottleneck = 1; while(g->bottleneck && g->waitCount < g->nThreads - 1) { @@ -111,12 +191,39 @@ static void bottleneck() UNLOCK(); naSemDown(g->sem); LOCK(); g->waitCount--; } +#if GC_DETAIL_DEBUG + printf("GC: wait %2d ", global_elapsedUSec()); +#endif if(g->waitCount >= g->nThreads - 1) { - freeDead(); - if(g->needGC) garbageCollect(); + int fd = freeDead(); +#if GC_DETAIL_DEBUG + printf("--> freedead (%5d) : %5d", fd, global_elapsedUSec()); +#endif + if(g->needGC) + garbageCollect(); if(g->waitCount) naSemUp(g->sem, g->waitCount); g->bottleneck = 0; } +#if GC_DETAIL_DEBUG + printf(" :: finished: %5d\n", global_elapsedUSec()); +#endif +} + +static void bottleneckFreeDead() +{ + global_stamp(); + struct Globals* g = globals; + g->bottleneck = 1; + while (g->bottleneck && g->waitCount < g->nThreads - 1) { + g->waitCount++; + UNLOCK(); naSemDown(g->sem); LOCK(); + g->waitCount--; + } + if (g->waitCount >= g->nThreads - 1) { + freeDead(); + if (g->waitCount) naSemUp(g->sem, g->waitCount); + g->bottleneck = 0; + } } void naGC() @@ -127,6 +234,29 @@ void naGC() UNLOCK(); naCheckBottleneck(); } +int naGarbageCollect() +{ + int rv = 1; + LOCK(); + // + // The number here is again based on observation - if this is too low then the inline GC will be used + // which is fine occasionally. + // So what we're doing by checking the global alloc is to see if GC is likely required during the next frame and if + // so we pre-empt this by doing it now. + // GC can typically take between 5ms and 50ms (F-15, FG1000 PFD & MFD, Advanced weather) - but usually it is completed + // prior to the start of the next frame. + + globals->needGC = nasal_globals->allocCount < 23000; + if (globals->needGC) + bottleneck(); + else { + bottleneckFreeDead(); + rv = 0; + } + UNLOCK(); + naCheckBottleneck(); + return rv; +} void naCheckBottleneck() { @@ -207,7 +337,9 @@ static int poolsize(struct naPool* p) while(b) { total += b->size; b = b->next; } return total; } - +int GCglobalAlloc() { + return globals->allocCount; +} struct naObj** naGC_get(struct naPool* p, int n, int* nout) { struct naObj** result; @@ -215,6 +347,9 @@ struct naObj** naGC_get(struct naPool* p, int n, int* nout) LOCK(); while(globals->allocCount < 0 || (p->nfree == 0 && p->freetop >= p->freesz)) { globals->needGC = 1; +#if GC_DETAIL_DEBUG + printf("++"); +#endif bottleneck(); } if(p->nfree == 0) @@ -248,7 +383,7 @@ static void mark(naRef r) if(PTR(r).obj->mark == 1) return; - + __elements_visited++; PTR(r).obj->mark = 1; switch(PTR(r).obj->type) { case T_VEC: markvec(r); break; @@ -306,11 +441,14 @@ static void reap(struct naPool* p) // Allocate more if necessary (try to keep 25-50% of the objects // available) - if(p->nfree < total/4) { + // This was changed (2019.2) to allocate in larger blocks + // previously it used total/4 and used/2 now we + // use total/2 and used / 1 + if (p->nfree < total / 2) { int used = total - p->nfree; int avail = total - used; - int need = used/2 - avail; - if(need > 0) + int need = used / 1 - avail; + if (need > 0) newBlock(p, need); } } diff --git a/simgear/nasal/lib.c b/simgear/nasal/lib.c index 796c3ca3..0322492f 100644 --- a/simgear/nasal/lib.c +++ b/simgear/nasal/lib.c @@ -295,25 +295,36 @@ static naRef f_die(naContext c, naRef me, int argc, naRef* args) return naNil(); // never executes } -// Wrapper around vsnprintf, iteratively increasing the buffer size -// until it fits. Returned buffer should be freed by the caller. +// Wrapper around vsnprintf that will allocate the required size +// by calling vsnprintf with NULL and 0 - and vsnsprintf will measure the +// required amount of characters which we then allocate and return +// Returned buffer should be freed by the caller. static char* dosprintf(char* f, ...) { char* buf; + va_list va; - int olen, len = 16; - while(1) { - buf = naAlloc(len); - va_start(va, f); - olen = vsnprintf(buf, len, f, va); - if(olen >= 0 && olen < len) { - va_end(va); - return buf; - } - va_end(va); - naFree(buf); - len *= 2; + va_start(va, f); + + int len = vsnprintf(0, 0, f, va); + va_end(va); + + if (len <= 0) { + buf = (char *) naAlloc(2); + *buf = 0; } + else { + len++;// allow for terminating null + buf = (char *) naAlloc(len); + + va_list va; + va_start(va, f); + + len = vsnprintf(buf, len, f, va); + va_end(va); + } + + return buf; } // Inspects a printf format string f, and finds the next "%..." format diff --git a/simgear/props/PropertyBasedMgr.hxx b/simgear/props/PropertyBasedMgr.hxx index 9a14dab5..1d40326a 100644 --- a/simgear/props/PropertyBasedMgr.hxx +++ b/simgear/props/PropertyBasedMgr.hxx @@ -29,74 +29,71 @@ namespace simgear { - class PropertyBasedMgr: - public SGSubsystem, - public SGPropertyChangeListener - { - public: - void init() override; - void shutdown() override; +class PropertyBasedMgr : public SGSubsystem, + public SGPropertyChangeListener +{ +public: + // Subsystem API. + void init() override; + void shutdown() override; + void update(double delta_time_sec) override; - void update (double delta_time_sec) override; + /** + * Create a new PropertyBasedElement + * + * @param name Name of the new element + */ + PropertyBasedElementPtr createElement(const std::string& name = ""); - /** - * Create a new PropertyBasedElement - * - * @param name Name of the new element - */ - PropertyBasedElementPtr createElement(const std::string& name = ""); + /** + * Get an existing PropertyBasedElement by its index + * + * @param index Index of element node in property tree + */ + PropertyBasedElementPtr getElement(size_t index) const; - /** - * Get an existing PropertyBasedElement by its index - * - * @param index Index of element node in property tree - */ - PropertyBasedElementPtr getElement(size_t index) const; + /** + * Get an existing PropertyBasedElement by its name + * + * @param name Name (value of child node "name" will be matched) + */ + PropertyBasedElementPtr getElement(const std::string& name) const; - /** - * Get an existing PropertyBasedElement by its name - * - * @param name Name (value of child node "name" will be matched) - */ - PropertyBasedElementPtr getElement(const std::string& name) const; + virtual const SGPropertyNode* getPropertyRoot() const; - virtual const SGPropertyNode* getPropertyRoot() const; +protected: + typedef boost::function + ElementFactory; - protected: + /** Branch in the property tree for this property managed subsystem */ + SGPropertyNode_ptr _props; - typedef boost::function - ElementFactory; + /** Property name of managed elements */ + const std::string _name_elements; - /** Branch in the property tree for this property managed subsystem */ - SGPropertyNode_ptr _props; + /** The actually managed elements */ + std::vector _elements; - /** Property name of managed elements */ - const std::string _name_elements; + /** Function object which creates a new element */ + ElementFactory _element_factory; - /** The actually managed elements */ - std::vector _elements; + /** + * @param props Root node of property branch used for controlling + * this subsystem + * @param name_elements The name of the nodes for the managed elements + */ + PropertyBasedMgr( SGPropertyNode_ptr props, + const std::string& name_elements, + ElementFactory element_factory ); + virtual ~PropertyBasedMgr() = 0; - /** Function object which creates a new element */ - ElementFactory _element_factory; - - /** - * @param props Root node of property branch used for controlling - * this subsystem - * @param name_elements The name of the nodes for the managed elements - */ - PropertyBasedMgr( SGPropertyNode_ptr props, - const std::string& name_elements, - ElementFactory element_factory ); - virtual ~PropertyBasedMgr() = 0; - - void childAdded( SGPropertyNode * parent, + void childAdded( SGPropertyNode * parent, + SGPropertyNode * child ) override; + void childRemoved( SGPropertyNode * parent, SGPropertyNode * child ) override; - void childRemoved( SGPropertyNode * parent, - SGPropertyNode * child ) override; - virtual void elementCreated(PropertyBasedElementPtr element) {} - - }; + virtual void elementCreated(PropertyBasedElementPtr element) {} +}; } // namespace simgear diff --git a/simgear/props/PropertyInterpolationMgr.hxx b/simgear/props/PropertyInterpolationMgr.hxx index efd907d9..429d2933 100644 --- a/simgear/props/PropertyInterpolationMgr.hxx +++ b/simgear/props/PropertyInterpolationMgr.hxx @@ -28,119 +28,113 @@ #include -namespace simgear +namespace simgear { + +/** + * Subsystem that manages interpolation of properties. + * + * By default the numeric values of the properties are interpolated. For + * example, for strings this is probably not the wanted behavior. For this + * adapter classes can be registered to allow providing specific + * interpolations for certain types of properties. Using the type "color", + * provided by ColorInterpolator, strings containing %CSS colors can also be + * interpolated. + * + * Additionally different functions can be used for easing of the animation. + * By default "linear" (constant animation speed) and "swing" (smooth + * acceleration and deceleration) are available. + */ +class PropertyInterpolationMgr : public SGSubsystem { +public: + typedef PropertyInterpolator* (*InterpolatorFactory)(); - /** - * Subsystem that manages interpolation of properties. - * - * By default the numeric values of the properties are interpolated. For - * example, for strings this is probably not the wanted behavior. For this - * adapter classes can be registered to allow providing specific - * interpolations for certain types of properties. Using the type "color", - * provided by ColorInterpolator, strings containing %CSS colors can also be - * interpolated. - * - * Additionally different functions can be used for easing of the animation. - * By default "linear" (constant animation speed) and "swing" (smooth - * acceleration and deceleration) are available. - */ - class PropertyInterpolationMgr: - public SGSubsystem - { - public: - typedef PropertyInterpolator* (*InterpolatorFactory)(); + PropertyInterpolationMgr(); - PropertyInterpolationMgr(); + // Subsystem API. + void update(double dt) override; - /** - * Update all active interpolators. - */ - void update(double dt); + /** + * Create a new property interpolator. + * + * @note To actually use it the interpolator needs to be attached to a + * property using PropertyInterpolationMgr::interpolate. + * + * @param type Type of animation ("numeric", "color", etc.) + * @param target Property containing target value + * @param duration Duration if the animation (in seconds) + * @param easing Type of easing ("linear", "swing", etc.) + */ + PropertyInterpolator* + createInterpolator(const std::string& type, + const SGPropertyNode& target, + double duration, + const std::string& easing); - /** - * Create a new property interpolator. - * - * @note To actually use it the interpolator needs to be attached to a - * property using PropertyInterpolationMgr::interpolate. - * - * @param type Type of animation ("numeric", "color", etc.) - * @param target Property containing target value - * @param duration Duration if the animation (in seconds) - * @param easing Type of easing ("linear", "swing", etc.) - */ - PropertyInterpolator* - createInterpolator( const std::string& type, - const SGPropertyNode& target, - double duration, - const std::string& easing ); + /** + * Add animation of the given property from its current value to the + * target value of the interpolator. If no interpolator is given any + * existing animation of the given property is aborted. + * + * @param prop Property to be interpolated + * @param interp Interpolator used for interpolation + */ + bool interpolate(SGPropertyNode* prop, + PropertyInterpolatorRef interp = 0); - /** - * Add animation of the given property from its current value to the - * target value of the interpolator. If no interpolator is given any - * existing animation of the given property is aborted. - * - * @param prop Property to be interpolated - * @param interp Interpolator used for interpolation - */ - bool interpolate( SGPropertyNode* prop, - PropertyInterpolatorRef interp = 0 ); + bool interpolate(SGPropertyNode* prop, + const std::string& type, + const SGPropertyNode& target, + double duration, + const std::string& easing); - bool interpolate( SGPropertyNode* prop, - const std::string& type, - const SGPropertyNode& target, - double duration, - const std::string& easing ); + bool interpolate(SGPropertyNode* prop, + const std::string& type, + const PropertyList& values, + const double_list& deltas, + const std::string& easing); - bool interpolate( SGPropertyNode* prop, - const std::string& type, - const PropertyList& values, - const double_list& deltas, - const std::string& easing ); - - /** - * Register factory for interpolation type. - */ - void addInterpolatorFactory( const std::string& type, - InterpolatorFactory factory ); - template - void addInterpolatorFactory(const std::string& type) - { - addInterpolatorFactory - ( - type, - &simgear::make_new_derived + /** + * Register factory for interpolation type. + */ + void addInterpolatorFactory(const std::string& type, + InterpolatorFactory factory); + template + void addInterpolatorFactory(const std::string& type) + { + addInterpolatorFactory( + type, + &simgear::make_new_derived ); - } + } - /** - * Register easing function. - */ - void addEasingFunction(const std::string& type, easing_func_t func); + /** + * Register easing function. + */ + void addEasingFunction(const std::string& type, easing_func_t func); - /** - * Set property containing real time delta (not sim time) - * - * TODO better pass both deltas to all update methods... - */ - void setRealtimeProperty(SGPropertyNode* node); + /** + * Set property containing real time delta (not sim time) + * + * TODO better pass both deltas to all update methods... + */ + void setRealtimeProperty(SGPropertyNode* node); - protected: +protected: + typedef std::map InterpolatorFactoryMap; + typedef std::map EasingFunctionMap; + typedef std::pair PropertyInterpolatorPair; + typedef std::list InterpolatorList; - typedef std::map InterpolatorFactoryMap; - typedef std::map EasingFunctionMap; - typedef std::pair< SGPropertyNode*, - PropertyInterpolatorRef > PropertyInterpolatorPair; - typedef std::list InterpolatorList; + struct PredicateIsSameProp; - struct PredicateIsSameProp; + InterpolatorFactoryMap _interpolator_factories; + EasingFunctionMap _easing_functions; + InterpolatorList _interpolators; - InterpolatorFactoryMap _interpolator_factories; - EasingFunctionMap _easing_functions; - InterpolatorList _interpolators; - - SGPropertyNode_ptr _rt_prop; - }; + SGPropertyNode_ptr _rt_prop; +}; } // namespace simgear diff --git a/simgear/scene/dem/SGDemSession.cxx b/simgear/scene/dem/SGDemSession.cxx index fb95caec..391b8c6f 100644 --- a/simgear/scene/dem/SGDemSession.cxx +++ b/simgear/scene/dem/SGDemSession.cxx @@ -1,79 +1,86 @@ #include #include -SGDemSession::SGDemSession( int mnLon, int mnLat, int mxLon, int mxLat, int idx, int lvlW, int lvlH, SGDemRoot* root ) +SGDemSession::SGDemSession(int mnLon, int mnLat, int mxLon, int mxLat, int idx, int lvlW, int lvlH, SGDemRoot* root) { - setOffsets( SGDem::longitudeDegToOffset((double)mnLon), - SGDem::latitudeDegToOffset((double)mnLat), - SGDem::longitudeDegToOffset((double)mxLon), - SGDem::latitudeDegToOffset((double)mxLat) ); + setOffsets(SGDem::longitudeDegToOffset((double)mnLon), + SGDem::latitudeDegToOffset((double)mnLat), + SGDem::longitudeDegToOffset((double)mxLon), + SGDem::latitudeDegToOffset((double)mxLat)); - pDemRoot = root; - lvlIndex = idx; - lvlWidth = lvlW; + pDemRoot = root; + lvlIndex = idx; + lvlWidth = lvlW; lvlHeight = lvlH; } -SGDemSession::SGDemSession( int mnLon, int mnLat, int mxLon, int mxLat, SGDemRoot* root ) { - setOffsets( SGDem::longitudeDegToOffset((double)mnLon), - SGDem::latitudeDegToOffset((double)mnLat), - SGDem::longitudeDegToOffset((double)mxLon), - SGDem::latitudeDegToOffset((double)mxLat) ); +SGDemSession::SGDemSession(int mnLon, int mnLat, int mxLon, int mxLat, SGDemRoot* root) +{ + setOffsets(SGDem::longitudeDegToOffset((double)mnLon), + SGDem::latitudeDegToOffset((double)mnLat), + SGDem::longitudeDegToOffset((double)mxLon), + SGDem::latitudeDegToOffset((double)mxLat)); - pDemRoot = root; - lvlIndex = -1; // no level - session is raw input dir + pDemRoot = root; + lvlIndex = -1; // no level - session is raw input dir } -void SGDemSession::close( void ) +void SGDemSession::close(void) { - if ( tileRefs.size() ) { + if (tileRefs.size()) { tileRefs.clear(); - if ( lvlIndex >= 0 ) { - pDemRoot->flushCaches( lvlIndex ); + if (lvlIndex >= 0) { + pDemRoot->flushCaches(lvlIndex); } } } -void SGDemSession::getGeods( unsigned wo, unsigned so, unsigned eo, unsigned no, int resx, int resy, int incx, int incy, ::std::vector& geods, bool Debug1, bool Debug2 ) +void SGDemSession::getGeods(unsigned wo, unsigned so, unsigned eo, unsigned no, int resx, int resy, int incx, int incy, ::std::vector& geods, bool Debug1, bool Debug2) { // todo - store this info in deminfo unsigned span; // smallest tile width/height in level ( in offsets ) - switch( lvlIndex ) { - case 0: span = 1; break; // 1/8 deg - case 1: span = 16; break; // 2 degrees - case 2: span = 480; break; // 60 degrees - default: - fprintf( stderr, "invalid lvlIndex %d\n", lvlIndex ); - exit(0); + switch (lvlIndex) { + case 0: + span = 1; + break; // 1/8 deg + case 1: + span = 16; + break; // 2 degrees + case 2: + span = 480; + break; // 60 degrees + default: + fprintf(stderr, "invalid lvlIndex %d\n", lvlIndex); + exit(0); } - if ( lvlIndex >= 0 ) { + if (lvlIndex >= 0) { unsigned tileLon, tileLat; unsigned meshLon, meshLat; - int subx, suby; + int subx, suby; meshLon = wo; - tileLon = SGDem::roundDown( meshLon, lvlWidth); + tileLon = SGDem::roundDown(meshLon, lvlWidth); subx = (meshLon - tileLon) / span; // fprintf(stderr, "getGeods: lon is %lf : meshLon is %u, tileLon is %u, subx is %d\n", SGDem::offsetToLongitudeDeg(wo), meshLon, tileLon, subx ); meshLat = so; - tileLat = SGDem::roundDown( meshLat, lvlHeight ); + tileLat = SGDem::roundDown(meshLat, lvlHeight); suby = (meshLat - tileLat) / span; // fprintf(stderr, "getGeods: lat is %lf : meshLat is %u, tileLat is %u, suby is %d\n", SGDem::offsetToLatitudeDeg(so), meshLat, tileLat, suby ); // get the tle from the tile cache unsigned long key = tileLon << 16 | tileLat; - SGDemTileRef tile = pDemRoot->getTile( lvlIndex, key ); - if ( tile ) { + SGDemTileRef tile = pDemRoot->getTile(lvlIndex, key); + if (tile) { tile->getGeods(wo, so, eo, no, resx, resy, subx, suby, incx, incy, geods, Debug1, Debug2); } else { - fprintf(stderr, " *** ERROR: tile %d,%d not in session @ (%lf,%lf) - (%lf,%lf)\n", + fprintf(stderr, " *** ERROR: tile %u,%u not in session @ (%lf,%lf) - (%lf,%lf)\n", tileLon, tileLat, - SGDem::offsetToLongitudeDeg( west_off ), - SGDem::offsetToLatitudeDeg( south_off ), - SGDem::offsetToLongitudeDeg( east_off ), - SGDem::offsetToLatitudeDeg( north_off ) ); + SGDem::offsetToLongitudeDeg(west_off), + SGDem::offsetToLatitudeDeg(south_off), + SGDem::offsetToLongitudeDeg(east_off), + SGDem::offsetToLatitudeDeg(north_off)); } } } diff --git a/simgear/scene/dem/SGDemSession.hxx b/simgear/scene/dem/SGDemSession.hxx index f0813556..3d5cddc6 100644 --- a/simgear/scene/dem/SGDemSession.hxx +++ b/simgear/scene/dem/SGDemSession.hxx @@ -3,58 +3,83 @@ #include -class SGDemSession -{ +class SGDemSession { public: - SGDemSession() { - lvlIndex = -1; + SGDemSession() + : west_off(0) + , south_off(0) + , east_off(0) + , north_off(0) + , maxLon(0) + , maxLat(0) + , pDemRoot(NULL) + , lvlIndex(-1) + , lvlWidth(0) + , lvlHeight(0) + { } - SGDemSession( int mnLon, int mnLat, int mxLon, int mxLat, int idx, int lvlW, int lvlH, SGDemRoot* root ); - SGDemSession( int mnLon, int mnLat, int mxLon, int mxLat, SGDemRoot* root ); + SGDemSession(int mnLon, int mnLat, int mxLon, int mxLat, int idx, int lvlW, int lvlH, SGDemRoot* root); + SGDemSession(int mnLon, int mnLat, int mxLon, int mxLat, SGDemRoot* root); - SGDemSession( unsigned wo, unsigned so, unsigned eo, unsigned no, int idx, unsigned lvlW, unsigned lvlH, SGDemRoot* root ) { - setOffsets( wo, so, eo, no ); - - pDemRoot = root; - lvlIndex = idx; - lvlWidth = lvlW; - lvlHeight = lvlH; + SGDemSession(unsigned wo, unsigned so, unsigned eo, unsigned no, int idx, unsigned lvlW, unsigned lvlH, SGDemRoot* root) + : west_off(wo) + , south_off(so) + , east_off(eo) + , north_off(no) + , maxLon(0) + , maxLat(0) + , pDemRoot(root) + , lvlIndex(idx) + , lvlWidth(lvlW) + , lvlHeight(lvlH) + { } - SGDemSession( unsigned wo, unsigned so, unsigned eo, unsigned no, SGDemRoot* root ) { - setOffsets( wo, so, eo, no ); - - pDemRoot = root; - lvlIndex = -1; // no level - session is raw input dir + SGDemSession(unsigned wo, unsigned so, unsigned eo, unsigned no, SGDemRoot* root) + : west_off(wo) + , south_off(so) + , east_off(eo) + , north_off(no) + , maxLon(0) + , maxLat(0) + , pDemRoot(root) + , lvlIndex(-1) // no level - session is raw input dir + , lvlWidth(0) + , lvlHeight(0) + { } - ~SGDemSession() { + ~SGDemSession() + { close(); } - void addTile(SGDemTileRef pTile) { - tileRefs.push_back( pTile ); + void addTile(SGDemTileRef pTile) + { + tileRefs.push_back(pTile); } - const std::vector& getTiles( void ) const { + const std::vector& getTiles(void) const + { return tileRefs; } - unsigned int size( void ) const { + unsigned int size(void) const + { return tileRefs.size(); } - void getGeods( unsigned wp, unsigned so, unsigned eo, unsigned no, - int resx, int resy, int incx, int incy, - ::std::vector& geods, - bool Debug1, bool Debug2 - ); + void getGeods(unsigned wp, unsigned so, unsigned eo, unsigned no, + int resx, int resy, int incx, int incy, + ::std::vector& geods, + bool Debug1, bool Debug2); - void close( void ); + void close(void); - int getLvlIndex( void ) const { - return lvlIndex; + int getLvlIndex(void) const + { + return lvlIndex; }; private: @@ -64,13 +89,13 @@ private: east_off = eo; north_off = no; } - - unsigned west_off, south_off; - unsigned east_off, north_off; - int maxLon, maxLat; - SGDemRoot* pDemRoot; - int lvlIndex; - unsigned lvlWidth, lvlHeight; + + unsigned west_off, south_off; + unsigned east_off, north_off; + int maxLon, maxLat; + SGDemRoot* pDemRoot; + int lvlIndex; + unsigned lvlWidth, lvlHeight; std::vector tileRefs; }; diff --git a/simgear/scene/material/Effect.cxx b/simgear/scene/material/Effect.cxx index 72f5fba1..c2240c05 100644 --- a/simgear/scene/material/Effect.cxx +++ b/simgear/scene/material/Effect.cxx @@ -77,6 +77,7 @@ #include #include #include +#include #include #include #include @@ -245,11 +246,12 @@ int Effect::getGenerator(Effect::Generator what) const // There should always be a valid technique in an effect. -Technique* Effect::chooseTechnique(RenderInfo* info) +Technique* Effect::chooseTechnique(RenderInfo* info, const std::string &scheme) { BOOST_FOREACH(ref_ptr& technique, techniques) { - if (technique->valid(info) == Technique::VALID) + if (technique->valid(info) == Technique::VALID && + technique->getScheme() == scheme) return technique.get(); } return 0; @@ -976,6 +978,13 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass, type); program->setParameter(GL_GEOMETRY_OUTPUT_TYPE_EXT, type); } + PropertyList pUniformBlockBindings + = prop->getChildren("uniform-block-binding"); + for (const auto &pUniformBlockBinding : pUniformBlockBindings) { + program->addBindUniformBlock( + pUniformBlockBinding->getStringValue("name"), + pUniformBlockBinding->getIntValue("index")); + } programMap.insert(ProgramMap::value_type(prgKey, program)); resolvedProgramMap.insert(ProgramMap::value_type(resolvedKey, program)); pass->setAttributeAndModes(program); @@ -1308,6 +1317,7 @@ void buildTechnique(Effect* effect, const SGPropertyNode* prop, { Technique* tniq = new Technique; effect->techniques.push_back(tniq); + tniq->setScheme(prop->getStringValue("scheme")); const SGPropertyNode* predProp = prop->getChild("predicate"); if (!predProp) { tniq->setAlwaysValid(true); @@ -1411,12 +1421,60 @@ bool makeParametersFromStateSet(SGPropertyNode* effectRoot, const StateSet* ss) return true; } +SGPropertyNode_ptr schemeList; + +void mergeSchemesFallbacks(Effect *effect, const SGReaderWriterOptions *options) +{ + if (!schemeList) { + schemeList = new SGPropertyNode; + const string schemes_file("Effects/schemes.xml"); + string absFileName + = SGModelLib::findDataFile(schemes_file, options); + if (absFileName.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "Could not find '" << schemes_file << "'"); + return; + } + try { + readProperties(absFileName, schemeList, 0, true); + } catch (sg_io_exception& e) { + SG_LOG(SG_INPUT, SG_ALERT, "Error reading '" << schemes_file << + "': " << e.getFormattedMessage()); + return; + } + } + + PropertyList p_schemes = schemeList->getChildren("scheme"); + for (const auto &p_scheme : p_schemes) { + string scheme_name = p_scheme->getStringValue("name"); + string fallback_name = p_scheme->getStringValue("fallback"); + if (scheme_name.empty() || fallback_name.empty()) + continue; + vector techniques = effect->root->getChildren("technique"); + auto it = std::find_if(techniques.begin(), techniques.end(), + [&scheme_name](const SGPropertyNode_ptr &tniq) { + return tniq->getStringValue("scheme") == scheme_name; + }); + // Only merge the fallback effect if we haven't found a technique + // implementing the scheme + if (it == techniques.end()) { + ref_ptr fallback = makeEffect(fallback_name, false, options); + if (fallback) { + SGPropertyNode *new_root = new SGPropertyNode; + mergePropertyTrees(new_root, effect->root, fallback->root); + effect->root = new_root; + effect->parametersProp = effect->root->getChild("parameters"); + } + } + } +} + // Walk the techniques property tree, building techniques and // passes. static SGMutex realizeTechniques_lock; bool Effect::realizeTechniques(const SGReaderWriterOptions* options) { SGGuard g(realizeTechniques_lock); + mergeSchemesFallbacks(this, options); if (_isRealized) return true; diff --git a/simgear/scene/material/Effect.hxx b/simgear/scene/material/Effect.hxx index 56a84769..496805a2 100644 --- a/simgear/scene/material/Effect.hxx +++ b/simgear/scene/material/Effect.hxx @@ -92,7 +92,7 @@ public: SGPropertyNode_ptr root; // Pointer to the parameters node, if it exists SGPropertyNode_ptr parametersProp; - Technique* chooseTechnique(osg::RenderInfo* renderInfo); + Technique* chooseTechnique(osg::RenderInfo* renderInfo, const std::string &scheme); virtual void resizeGLObjectBuffers(unsigned int maxSize); virtual void releaseGLObjects(osg::State* state = 0) const; /** diff --git a/simgear/scene/material/EffectCullVisitor.cxx b/simgear/scene/material/EffectCullVisitor.cxx index a016a455..b25012a0 100644 --- a/simgear/scene/material/EffectCullVisitor.cxx +++ b/simgear/scene/material/EffectCullVisitor.cxx @@ -21,6 +21,8 @@ #include #include +#include + #include "EffectCullVisitor.hxx" #include "EffectGeode.hxx" @@ -34,9 +36,9 @@ namespace simgear using osgUtil::CullVisitor; -EffectCullVisitor::EffectCullVisitor(bool collectLights, Effect *effectOverride) : +EffectCullVisitor::EffectCullVisitor(bool collectLights, const std::string &effScheme) : _collectLights(collectLights), - _effectOverride(effectOverride) + _effScheme(effScheme) { } @@ -50,6 +52,19 @@ CullVisitor* EffectCullVisitor::clone() const return new EffectCullVisitor(*this); } +void EffectCullVisitor::apply(osg::Node &node) +{ + // TODO: Properly cull lights outside the viewport (override computeBounds()) + // if (isCulled(node)) + // return; + SGLight *light = dynamic_cast(&node); + if (!light) { + CullVisitor::apply(node); + return; + } + _lightList.push_back(light); +} + void EffectCullVisitor::apply(osg::Geode& node) { if (isCulled(node)) @@ -59,21 +74,12 @@ void EffectCullVisitor::apply(osg::Geode& node) CullVisitor::apply(node); return; } - if (_collectLights && ( eg->getNodeMask() & MODELLIGHT_BIT ) ) { - _lightList.push_back( eg ); - } - Effect *effect; - if (_effectOverride) { - effect = _effectOverride; - } else { - effect = eg->getEffect(); - if (!effect) { - CullVisitor::apply(node); - return; - } - } + Effect* effect = eg->getEffect(); Technique* technique = 0; - if (!(technique = effect->chooseTechnique(&getRenderInfo()))) { + if (!effect) { + CullVisitor::apply(node); + return; + } else if (!(technique = effect->chooseTechnique(&getRenderInfo(), _effScheme))) { return; } // push the node's state. diff --git a/simgear/scene/material/EffectCullVisitor.hxx b/simgear/scene/material/EffectCullVisitor.hxx index ef93baa3..3669126b 100644 --- a/simgear/scene/material/EffectCullVisitor.hxx +++ b/simgear/scene/material/EffectCullVisitor.hxx @@ -21,6 +21,8 @@ #include +#include + namespace osg { class Geode; @@ -34,10 +36,11 @@ class EffectGeode; class EffectCullVisitor : public osgUtil::CullVisitor { public: - EffectCullVisitor(bool collectLights = false, Effect *effectOverride = 0); + EffectCullVisitor(bool collectLights = false, const std::string &effScheme = ""); EffectCullVisitor(const EffectCullVisitor&); virtual osgUtil::CullVisitor* clone() const; using osgUtil::CullVisitor::apply; + virtual void apply(osg::Node& node); virtual void apply(osg::Geode& node); virtual void reset(); @@ -45,11 +48,13 @@ public: void addBuffer(std::string b, osg::Texture2D* tex); osg::Texture2D* getBuffer(std::string b); + SGLightList getLightList() const { return _lightList; } + private: std::map > _bufferList; - std::vector > _lightList; + SGLightList _lightList; bool _collectLights; - osg::ref_ptr _effectOverride; + std::string _effScheme; }; } #endif diff --git a/simgear/scene/material/Technique.hxx b/simgear/scene/material/Technique.hxx index b2895cd1..6cca0a05 100644 --- a/simgear/scene/material/Technique.hxx +++ b/simgear/scene/material/Technique.hxx @@ -98,6 +98,8 @@ public: void setGLExtensionsPred(float glVersion, const std::vector& extensions); void refreshValidity(); + const std::string &getScheme() const { return _scheme; } + void setScheme(const std::string &scheme) { _scheme = scheme; } protected: // Validity of technique in a graphics context. struct ContextInfo : public osg::Referenced @@ -117,6 +119,7 @@ protected: osg::ref_ptr _shadowingStateSet; SGSharedPtr _validExpression; int _contextIdLocation; + std::string _scheme; }; class TechniquePredParser : public expression::ExpressionParser diff --git a/simgear/scene/material/makeEffect.cxx b/simgear/scene/material/makeEffect.cxx index cfbb87af..35591c51 100644 --- a/simgear/scene/material/makeEffect.cxx +++ b/simgear/scene/material/makeEffect.cxx @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -126,7 +127,12 @@ Effect* makeEffect(const string& name, itr->second.valid()) return itr->second.get(); } - string effectFileName(name); + string effectFileName; + // Use getPropertyRoot() because the SGReaderWriterOptions might not have a + // valid property tree + if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false)) + effectFileName += "Compositor/"; + effectFileName += name; effectFileName += ".eff"; string absFileName = SGModelLib::findDataFile(effectFileName, options); diff --git a/simgear/scene/model/CMakeLists.txt b/simgear/scene/model/CMakeLists.txt index 69bb545f..50227941 100644 --- a/simgear/scene/model/CMakeLists.txt +++ b/simgear/scene/model/CMakeLists.txt @@ -10,6 +10,7 @@ set(HEADERS PrimitiveCollector.hxx SGClipGroup.hxx SGInteractionAnimation.hxx + SGLight.hxx SGMaterialAnimation.hxx SGPickAnimation.hxx SGOffsetTransform.hxx @@ -35,6 +36,7 @@ set(SOURCES PrimitiveCollector.cxx SGClipGroup.cxx SGInteractionAnimation.cxx + SGLight.cxx SGLightAnimation.cxx SGPickAnimation.cxx SGMaterialAnimation.cxx @@ -62,4 +64,4 @@ if(ENABLE_TESTS) target_link_libraries(test_animations ${TEST_LIBS} ${OPENSCENEGRAPH_LIBRARIES}) add_test(animations ${EXECUTABLE_OUTPUT_PATH}/test_animations) -endif(ENABLE_TESTS) \ No newline at end of file +endif(ENABLE_TESTS) diff --git a/simgear/scene/model/ModelRegistry.cxx b/simgear/scene/model/ModelRegistry.cxx index 651a4ad7..0087cbbf 100644 --- a/simgear/scene/model/ModelRegistry.cxx +++ b/simgear/scene/model/ModelRegistry.cxx @@ -62,6 +62,7 @@ #include #include #include +#include #include "BoundingVolumeBuildVisitor.hxx" #include "model.hxx" @@ -182,7 +183,7 @@ public: } // namespace -static int nearestPowerOfTwo(unsigned int _v) +static int nearestPowerOfTwo(int _v) { // uint v; // compute the next highest power of 2 of 32-bit v unsigned int v = (unsigned int)_v; @@ -207,10 +208,12 @@ static int nearestPowerOfTwo(unsigned int _v) _v = (int)v; return v; } - static bool isPowerOfTwo(int v) + +static bool isPowerOfTwo(int v) { return ((v & (v - 1)) == 0); } + osg::Node* DefaultProcessPolicy::process(osg::Node* node, const std::string& filename, const Options* opt) { @@ -232,144 +235,28 @@ osg::Image* getImageByName(const std::string& filename) return nullptr; } #endif -// a cache which evicts the least recently used item when it is full -#include -#include -#include -#include -template -class lru_cache -{ -public: - SGMutex _mutex; +// least recently used cache to speed up the process of finding a file +// after the first time - otherwise each time we'll have to generate a hash +// of the contents. +static lru_cache < std::string, std::string> filename_hash_cache(100000); - typedef Key key_type; - typedef Value value_type; - typedef std::list list_type; - typedef std::map< - key_type, - std::pair - > map_type; - - lru_cache(size_t capacity) - : m_capacity(capacity) - { - } - - ~lru_cache() - { - } - - - size_t size() const - { - return m_map.size(); - } - - size_t capacity() const - { - return m_capacity; - } - - bool empty() const - { - return m_map.empty(); - } - - bool contains(const key_type &key) - { - SGGuard scopeLock(_mutex); - return m_map.find(key) != m_map.end(); - } - - void insert(const key_type &key, const value_type &value) - { - SGGuard scopeLock(_mutex); - typename map_type::iterator i = m_map.find(key); - if (i == m_map.end()) { - // insert item into the cache, but first check if it is full - if (size() >= m_capacity) { - // cache is full, evict the least recently used item - evict(); - } - - // insert the new item - m_list.push_front(key); - m_map[key] = std::make_pair(value, m_list.begin()); - } - } - boost::optional findValue(const std::string &requiredValue) - { - SGGuard scopeLock(_mutex); - for (typename map_type::iterator it = m_map.begin(); it != m_map.end(); ++it) - if (it->second.first == requiredValue) - return it->first; - return boost::none; - } - boost::optional get(const key_type &key) - { - SGGuard scopeLock(_mutex); - // lookup value in the cache - typename map_type::iterator i = m_map.find(key); - if (i == m_map.end()) { - // value not in cache - return boost::none; - } - - // return the value, but first update its place in the most - // recently used list - typename list_type::iterator j = i->second.second; - if (j != m_list.begin()) { - // move item to the front of the most recently used list - m_list.erase(j); - m_list.push_front(key); - - // update iterator in map - j = m_list.begin(); - const value_type &value = i->second.first; - m_map[key] = std::make_pair(value, j); - - // return the value - return value; - } - else { - // the item is already at the front of the most recently - // used list so just return it - return i->second.first; - } - } - - void clear() - { - SGGuard scopeLock(_mutex); - m_map.clear(); - m_list.clear(); - } - -private: - void evict() - { - SGGuard scopeLock(_mutex); - // evict item from the end of most recently used list - typename list_type::iterator i = --m_list.end(); - m_map.erase(*i); - m_list.erase(i); - } - -private: - map_type m_map; - list_type m_list; - size_t m_capacity; -}; -lru_cache < std::string, std::string> filename_hash_cache(100000); -lru_cache < std::string, bool> filesCleaned(100000); -static bool refreshCache = false; +// experimental (incomplete) features to allow maintenance of the filecache. +//lru_cache < std::string, bool> filesCleaned(100000); +//static bool refreshCache = false; ReaderWriter::ReadResult ModelRegistry::readImage(const string& fileName, const Options* opt) { + // experimental feature to see if we can reload textures during model load + // as otherwise texture creation/editting requires a restart or a change to + // a different filenaem + //if (SGSceneFeatures::instance()->getReloadCache()) { + // SG_LOG(SG_IO, SG_INFO, "Clearing DDS-TC LRU Cache"); + // filename_hash_cache.clear(); + // SGSceneFeatures::instance()->setReloadCache(false); + //} /* * processor is the interface to the osg_nvtt plugin */ @@ -423,13 +310,21 @@ ModelRegistry::readImage(const string& fileName, if (fileExists(absFileName)) { SGFile f(absFileName); std::string hash; + +// std::string attr = ""; +// if (sgoptC && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS_NORMALIZED) { +// attr += " effnorm"; +// } +// else if (sgoptC && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS) { +// attr += " eff"; +// // can_compress = false; +// } +// SG_LOG(SG_IO, SG_INFO, absFileName << attr); boost::optional cachehash = filename_hash_cache.get(absFileName); if (cachehash) { hash = *cachehash; - // SG_LOG(SG_IO, SG_ALERT, "Hash for " + absFileName + " in cache " + hash); } else { - // SG_LOG(SG_IO, SG_ALERT, "Creating hash for " + absFileName); try { hash = f.computeHash(); } @@ -444,9 +339,8 @@ ModelRegistry::readImage(const string& fileName, // 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_INFO, " Already have " + hash + " : " + *cacheFilename + " not " + absFileName); } - // SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); } newName = cache_root + "/" + hash.substr(0, 2) + "/" + hash + ".cache.dds"; } @@ -456,21 +350,15 @@ ModelRegistry::readImage(const string& fileName, newName += "." + tstream.str(); newName += ".cache.dds"; } - bool doRefresh = refreshCache; - //if (fileExists(newName) && sgoptC && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS) { - // doRefresh = true; - // + //bool doRefresh = refreshCache; + //if (newName != std::string() && fileExists(newName) && doRefresh) { + // if (!filesCleaned.contains(newName)) { + // SG_LOG(SG_IO, SG_INFO, "Removing previously cached effects image " + newName); + // SGPath(newName).remove(); + // filesCleaned.insert(newName, true); + // } //} - 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(); - filesCleaned.insert(newName, true); - } - - } - if (newName != std::string() && !fileExists(newName)) { res = registry->readImageImplementation(absFileName, opt); if (res.validImage()) { @@ -494,14 +382,15 @@ ModelRegistry::readImage(const string& fileName, isNormalMap = true; } else if (sgoptC && transparent && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS) { - SG_LOG(SG_IO, SG_ALERT, "From effects transparent " + absFileName); + SG_LOG(SG_IO, SG_INFO, "From effects transparent " + absFileName + " will generate mipmap only"); + isEffect = true; + can_compress = false; + } + else if (sgoptC && !transparent && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS) { + SG_LOG(SG_IO, SG_INFO, "From effects " + absFileName + " will generate mipmap only"); isEffect = true; // can_compress = false; } - else if (sgoptC && transparent && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS) { - SG_LOG(SG_IO, SG_ALERT, "From effects " + absFileName); - isEffect = true; - } if (can_compress) { std::string pot_message; @@ -516,8 +405,9 @@ ModelRegistry::readImage(const string& fileName, resize = true; pot_message += std::string(" not POT: resized height to ") + std::to_string(height); } -if (pot_message.size()) -SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); + + if (pot_message.size()) + SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); // unlikely that after resizing in height the width will still be outside of the max texture size. if (height > max_texture_size) @@ -536,6 +426,7 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); width /= factor; resize = true; } + if (resize) { osg::ref_ptr resizedImage; @@ -601,21 +492,22 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); // normal maps: // nvdxt.exe - quality_highest - rescaleKaiser - Kaiser - dxt5nm - norm processor->compress(*srcImage, targetFormat, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION); - SG_LOG(SG_IO, SG_ALERT, "-- finished creating DDS: " + newName); + SG_LOG(SG_IO, SG_INFO, "-- finished creating DDS: " + newName); //processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU); } else { simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE); - SG_LOG(SG_IO, SG_WARN, "Texture compression plugin (osg_nvtt) not available; storing uncompressed image: " << absFileName); + SG_LOG(SG_IO, SG_INFO, "Texture compression plugin (osg_nvtt) not available; storing uncompressed image: " << absFileName); srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions); } - } - else { - SG_LOG(SG_IO, SG_ALERT, "Creating uncompressed DDS for " + absFileName); - if (processor) { - processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU); } - else { + else { + SG_LOG(SG_IO, SG_INFO, "Creating uncompressed DDS for " + absFileName); + //if (processor) { + // processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU); + //} + //else + { simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE); srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions); } @@ -660,25 +552,28 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); osg::ref_ptr srcImage1 = res.getImage(); - //printf(" --> finished loading %s [%s] (%s) %d\n", absFileName.c_str(), srcImage1->getFileName().c_str(), res.loadedFromCache() ? "from cache" : "from disk", res.getImage()->getOrigin()); /* - * Fixup the filename - as when loading from eg. dds.gz the originating filename is lost in the conversion due to the way the OSG loader works - */ - if (srcImage1->getFileName().empty()) { - srcImage1->setFileName(absFileName); - } + * Fixup the filename - as when loading from eg. dds.gz the originating filename is lost in the conversion due to the way the OSG loader works + */ + //if (srcImage1->getFileName().empty()) { + // srcImage1->setFileName(absFileName); + //} srcImage1->setFileName(originalFileName); - if(cache_active && getFileExtension(absFileName) != "dds") + if(cache_active && getFileExtension(absFileName) != "dds"&& getFileExtension(absFileName) != "gz") { - if (processor) { - processor->generateMipMap(*srcImage1, true, osgDB::ImageProcessor::USE_CPU); - SG_LOG(SG_IO, SG_ALERT, "Created nvtt mipmaps DDS for " + absFileName); - } - else { + // In testing the internal mipmap generation works better than the external nvtt one + // (less artefacts); it might be that there are flags we can use to make this better + // but for now we'll just using the built in one + //if (processor) { + // processor->generateMipMap(*srcImage1, true, osgDB::ImageProcessor::USE_CPU); + // SG_LOG(SG_IO, SG_INFO, "Created nvtt mipmaps DDS for " + absFileName); + // } + // else + { simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE); srcImage1 = simgear::effect::computeMipmap(srcImage1, mipmapFunctions); - SG_LOG(SG_IO, SG_ALERT, "Created sg mipmaps DDS for " + absFileName); + SG_LOG(SG_IO, SG_DEBUG, "Created sg mipmaps DDS for " + absFileName); } } @@ -689,75 +584,6 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); SG_LOG(SG_IO, SG_BULK, "Reading image \"" << res.getImage()->getFileName() << "\""); - // as of March 2018 all patents have expired, https://en.wikipedia.org/wiki/S3_Texture_Compression#Patent - // there is support for S3TC DXT1..5 in MESA https://www.phoronix.com/scan.php?page=news_item&px=S3TC-Lands-In-Mesa - // so it seems that there isn't a valid reason to warn any longer; and beside this is one of those cases where it should - // really only be a developer message -#ifdef WARN_DDS_TEXTURES - // Check for precompressed textures that depend on an extension - switch (res.getImage()->getPixelFormat()) { - - // GL_EXT_texture_compression_s3tc - // patented, no way to decompress these -#ifndef GL_EXT_texture_compression_s3tc -#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 -#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 -#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 -#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 -#endif - case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - - // GL_EXT_texture_sRGB - // patented, no way to decompress these -#ifndef GL_EXT_texture_sRGB -#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C -#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D -#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E -#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F -#endif - case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: - case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: - case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: - case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: - - // GL_TDFX_texture_compression_FXT1 - // can decompress these in software but - // no code present in simgear. -#ifndef GL_3DFX_texture_compression_FXT1 -#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 -#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 -#endif - case GL_COMPRESSED_RGB_FXT1_3DFX: - case GL_COMPRESSED_RGBA_FXT1_3DFX: - - // GL_EXT_texture_compression_rgtc - // can decompress these in software but - // no code present in simgear. -#ifndef GL_EXT_texture_compression_rgtc -#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB -#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC -#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD -#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE -#endif - case GL_COMPRESSED_RED_RGTC1_EXT: - case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: - case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: - case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: - - SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n" - "uses compressed textures which cannot be supported on " - "some systems.\n" - "Please decompress this texture for improved portability."); - break; - - default: - break; - } -#endif - return res; } diff --git a/simgear/scene/model/SGLight.cxx b/simgear/scene/model/SGLight.cxx new file mode 100644 index 00000000..ab332750 --- /dev/null +++ b/simgear/scene/model/SGLight.cxx @@ -0,0 +1,157 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "SGLight.hxx" + +#include +#include +#include +#include +#include + +#include +#include + +class SGLightDebugListener : public SGPropertyChangeListener { +public: + SGLightDebugListener(osg::Switch *sw) : _sw(sw) {} + virtual void valueChanged(SGPropertyNode *node) { + _sw->setValue(0, node->getBoolValue()); + } +private: + osg::ref_ptr _sw; +}; + +osg::Node * +SGLight::appendLight(const SGPropertyNode *configNode, + SGPropertyNode *modelRoot, + const osgDB::Options *options) +{ + SGConstPropertyNode_ptr p; + + SGLight *light = new SGLight; + + if((p = configNode->getNode("type")) != NULL) { + std::string type = p->getStringValue(); + if (type == "point") + light->setType(SGLight::Type::POINT); + else if (type == "spot") + light->setType(SGLight::Type::SPOT); + else + SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown light type '" << type << "'"); + } + + light->setRange(configNode->getFloatValue("range-m")); + +#define SGLIGHT_GET_COLOR_VALUE(n) \ + osg::Vec4(configNode->getFloatValue(n "/r"), \ + configNode->getFloatValue(n "/g"), \ + configNode->getFloatValue(n "/b"), \ + configNode->getFloatValue(n "/a")) + light->setAmbient(SGLIGHT_GET_COLOR_VALUE("ambient")); + light->setDiffuse(SGLIGHT_GET_COLOR_VALUE("diffuse")); + light->setSpecular(SGLIGHT_GET_COLOR_VALUE("specular")); +#undef SGLIGHT_GET_COLOR_VALUE + + light->setConstantAttenuation(configNode->getFloatValue("attenuation/c")); + light->setLinearAttenuation(configNode->getFloatValue("attenuation/l")); + light->setQuadraticAttenuation(configNode->getFloatValue("attenuation/q")); + + light->setSpotExponent(configNode->getFloatValue("spot-exponent")); + light->setSpotCutoff(configNode->getFloatValue("spot-cutoff")); + + osg::Group *group = 0; + if ((p = configNode->getNode("offsets")) == NULL) { + group = new osg::Group; + } else { + // Set up the alignment node ("stolen" from animation.cxx) + // XXX Order of rotations is probably not correct. + osg::MatrixTransform *align = new osg::MatrixTransform; + osg::Matrix res_matrix; + res_matrix.makeRotate( + p->getFloatValue("pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 1, 0), + p->getFloatValue("roll-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(1, 0, 0), + p->getFloatValue("heading-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 0, 1)); + + osg::Matrix tmat; + tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0), + configNode->getFloatValue("offsets/y-m", 0.0), + configNode->getFloatValue("offsets/z-m", 0.0)); + + align->setMatrix(res_matrix * tmat); + group = align; + } + + group->addChild(light); + + osg::Shape *debug_shape; + if (light->getType() == SGLight::Type::POINT) { + debug_shape = new osg::Sphere(osg::Vec3(0, 0, 0), light->getRange()); + } else if (light->getType() == SGLight::Type::SPOT) { + debug_shape = new osg::Cone( + // Origin of the cone is at its center of mass + osg::Vec3(0, 0, -0.75 * light->getRange()), + tan(light->getSpotCutoff() * SG_DEGREES_TO_RADIANS) * light->getRange(), + light->getRange()); + } + osg::ShapeDrawable *debug_drawable = new osg::ShapeDrawable(debug_shape); + debug_drawable->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0)); + osg::Geode *debug_geode = new osg::Geode; + debug_geode->addDrawable(debug_drawable); + + osg::StateSet *debug_ss = debug_drawable->getOrCreateStateSet(); + debug_ss->setAttributeAndModes( + new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), + osg::StateAttribute::ON); + debug_ss->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + osg::Switch *debug_switch = new osg::Switch; + debug_switch->addChild(debug_geode); + simgear::getPropertyRoot()->getNode("/sim/debug/show-light-volumes", true)-> + addChangeListener(new SGLightDebugListener(debug_switch), true); + group->addChild(debug_switch); + + if ((p = configNode->getNode("name")) != NULL) + group->setName(p->getStringValue()); + else + group->setName("light"); + + return group; +} + +SGLight::SGLight() : + _type(Type::POINT), + _range(0.0f) +{ + // Default values taken from osg::Light + // They don't matter anyway as they are overwritten by the XML config values + _ambient.set(0.05f, 0.05f, 0.05f, 1.0f); + _diffuse.set(0.8f, 0.8f, 0.8f, 1.0f); + _specular.set(0.05f, 0.05f, 0.05f, 1.0f); + _constant_attenuation = 1.0f; + _linear_attenuation = 0.0f; + _quadratic_attenuation = 0.0f; + _spot_exponent = 0.0f; + _spot_cutoff = 180.0f; +} + +SGLight::~SGLight() +{ + +} diff --git a/simgear/scene/model/SGLight.hxx b/simgear/scene/model/SGLight.hxx new file mode 100644 index 00000000..2a25413b --- /dev/null +++ b/simgear/scene/model/SGLight.hxx @@ -0,0 +1,105 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_LIGHT_HXX +#define SG_LIGHT_HXX + +#include +#include + +#include + +class SGLight : public osg::Node { +public: + enum Type { + POINT, + SPOT + }; + + SGLight(); + + SGLight(const SGLight& l, + const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) : + osg::Node(l, copyop), + _type(l._type), + _range(l._range), + _ambient(l._ambient), + _diffuse(l._diffuse), + _specular(l._specular), + _constant_attenuation(l._constant_attenuation), + _linear_attenuation(l._linear_attenuation), + _quadratic_attenuation(l._quadratic_attenuation), + _spot_exponent(l._spot_exponent), + _spot_cutoff(l._spot_cutoff) + {} + + META_Node(simgear, SGLight); + + static osg::Node *appendLight(const SGPropertyNode *configNode, + SGPropertyNode *modelRoot, + const osgDB::Options *options); + + void setType(Type type) { _type = type; } + Type getType() const { return _type; } + + void setRange(float range) { _range = range; } + float getRange() const { return _range; } + + void setAmbient(const osg::Vec4 &ambient) { _ambient = ambient; } + const osg::Vec4 &getAmbient() const { return _ambient; } + + void setDiffuse(const osg::Vec4 &diffuse) { _diffuse = diffuse; } + const osg::Vec4 &getDiffuse() const { return _diffuse; } + + void setSpecular(const osg::Vec4 &specular) { _specular = specular; } + const osg::Vec4 &getSpecular() const { return _specular; } + + void setConstantAttenuation(float constant_attenuation) { _constant_attenuation = constant_attenuation; } + float getConstantAttenuation() const { return _constant_attenuation; } + + void setLinearAttenuation(float linear_attenuation) { _linear_attenuation = linear_attenuation; } + float getLinearAttenuation() const { return _linear_attenuation; } + + void setQuadraticAttenuation(float quadratic_attenuation) { _quadratic_attenuation = quadratic_attenuation; } + float getQuadraticAttenuation() const { return _quadratic_attenuation; } + + void setSpotExponent(float spot_exponent) { _spot_exponent = spot_exponent; } + float getSpotExponent() const { return _spot_exponent; } + + void setSpotCutoff(float spot_cutoff) { _spot_cutoff = spot_cutoff; } + float getSpotCutoff() const { return _spot_cutoff; } + +protected: + virtual ~SGLight(); + + Type _type; + + float _range; + + osg::Vec4 _ambient; + osg::Vec4 _diffuse; + osg::Vec4 _specular; + + float _constant_attenuation; + float _linear_attenuation; + float _quadratic_attenuation; + float _spot_exponent; + float _spot_cutoff; +}; + +typedef std::vector> SGLightList; + +#endif /* SG_LIGHT_HXX */ diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index c09a9579..b6fc37e9 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -50,6 +50,7 @@ #include "animation.hxx" #include "particles.hxx" #include "model.hxx" +#include "SGLight.hxx" #include "SGText.hxx" #include "SGMaterialAnimation.hxx" @@ -518,6 +519,14 @@ sgLoad3DModel_internal(const SGPath& path, options.get())); } + std::vector light_nodes; + light_nodes = props->getChildren("light"); + for (unsigned i = 0; i < light_nodes.size(); ++i) { + group->addChild(SGLight::appendLight(light_nodes[i], + prop_root, + options.get())); + } + PropertyList effect_nodes = props->getChildren("effect"); PropertyList animation_nodes = props->getChildren("animation"); diff --git a/simgear/scene/tsync/terrasync.cxx b/simgear/scene/tsync/terrasync.cxx index 69e381f2..a1715097 100644 --- a/simgear/scene/tsync/terrasync.cxx +++ b/simgear/scene/tsync/terrasync.cxx @@ -1115,3 +1115,9 @@ void SGTerraSync::reposition() { // stub, remove } + + +// Register the subsystem. +SGSubsystemMgr::Registrant registrantSGTerraSync( + SGSubsystemMgr::GENERAL, + {{"FGRenderer", SGSubsystemMgr::Dependency::NONSUBSYSTEM_HARD}}); diff --git a/simgear/scene/tsync/terrasync.hxx b/simgear/scene/tsync/terrasync.hxx index 4e806568..4d4c0e39 100644 --- a/simgear/scene/tsync/terrasync.hxx +++ b/simgear/scene/tsync/terrasync.hxx @@ -39,16 +39,19 @@ class BufferedLogCallback; class SGTerraSync : public SGSubsystem { public: - SGTerraSync(); virtual ~SGTerraSync(); - virtual void init(); - virtual void shutdown(); - virtual void reinit(); - virtual void bind(); - virtual void unbind(); - virtual void update(double); + // Subsystem API. + void bind() override; + void init() override; + void reinit() override; + void shutdown() override; + void unbind() override; + void update(double) override; + + // Subsystem identification. + static const char* staticSubsystemClassId() { return "terrasync"; } /// notify terrasync that the sim was repositioned, as opposed to /// us travelling in a direction. Avoid last_lat / last_lon blocking @@ -80,6 +83,7 @@ public: void scheduleDataDir(const std::string& dataDir); bool isDataDirPending(const std::string& dataDir) const; + protected: void syncAirportsModels(); string_list getSceneryPathSuffixes() const; diff --git a/simgear/scene/util/SGImageUtils.cxx b/simgear/scene/util/SGImageUtils.cxx index 7f79cd7b..58aad3b2 100644 --- a/simgear/scene/util/SGImageUtils.cxx +++ b/simgear/scene/util/SGImageUtils.cxx @@ -1812,7 +1812,7 @@ namespace { static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) { - GLubyte* ptr = (GLubyte*)iw->data(s, t, r, m); + iw->data(s, t, r, m); //OE_WARN << LC << "Target GL_UNSIGNED_BYTE_3_3_2 not yet implemented" << std::endl; } }; diff --git a/simgear/scene/viewer/CMakeLists.txt b/simgear/scene/viewer/CMakeLists.txt index d26297fc..ef223b56 100644 --- a/simgear/scene/viewer/CMakeLists.txt +++ b/simgear/scene/viewer/CMakeLists.txt @@ -1,5 +1,5 @@ set(HEADERS - ClusteredForward.hxx + ClusteredShading.hxx Compositor.hxx CompositorBuffer.hxx CompositorPass.hxx @@ -7,7 +7,7 @@ set(HEADERS ) set(SOURCES - ClusteredForward.cxx + ClusteredShading.cxx Compositor.cxx CompositorBuffer.cxx CompositorPass.cxx diff --git a/simgear/scene/viewer/ClusteredForward.cxx b/simgear/scene/viewer/ClusteredForward.cxx deleted file mode 100644 index 6e5f4098..00000000 --- a/simgear/scene/viewer/ClusteredForward.cxx +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (C) 2018 Fernando García Liñán -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Library General Public -// License as published by the Free Software Foundation; either -// version 2 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Library General Public License for more details. -// -// You should have received a copy of the GNU Library General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - -#include "ClusteredForward.hxx" - -#include -#include -#include -#include -#include -#include - -namespace simgear { -namespace compositor { - -///// BEGIN DEBUG -#define DATA_SIZE 24 -const GLfloat LIGHT_DATA[DATA_SIZE] = { - 0.0, 0.0, -10.0, 1.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 10.0, 1.0, 0.0, 1.0, 0.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0 -}; - -#define MAX_LIGHT_INDICES 4096 -#define MAX_POINT_LIGHTS 256 - -struct Light { - osg::Vec3 position; - float range; -}; - -#define NUM_LIGHTS 2 -Light LIGHT_LIST[NUM_LIGHTS] = { - {osg::Vec3(0.0f, 0.0f, -10.0f), 10.0f}, - {osg::Vec3(0.0f, 0.0f, 5.0f), 1000.0f} -}; -///// END DEBUG - -ClusteredForwardDrawCallback::ClusteredForwardDrawCallback() : - _initialized(false), - _tile_size(64), - _light_grid(new osg::Image), - _light_indices(new osg::Image), - _light_data(new osg::FloatArray(MAX_POINT_LIGHTS)) -{ -} - -void -ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const -{ - osg::Camera *camera = renderInfo.getCurrentCamera(); - const osg::Viewport *vp = camera->getViewport(); - const int width = vp->width(); - const int height = vp->height(); - - // Round up - int n_htiles = (width + _tile_size - 1) / _tile_size; - int n_vtiles = (height + _tile_size - 1) / _tile_size; - - if (!_initialized) { - // Create and associate the light grid 3D texture - _light_grid->allocateImage(n_htiles, n_vtiles, 1, - GL_RGB_INTEGER_EXT, GL_UNSIGNED_SHORT); - _light_grid->setInternalTextureFormat(GL_RGB16UI_EXT); - - osg::ref_ptr light_grid_tex = new osg::Texture3D; - light_grid_tex->setResizeNonPowerOfTwoHint(false); - light_grid_tex->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::CLAMP_TO_BORDER); - light_grid_tex->setWrap(osg::Texture3D::WRAP_S, osg::Texture3D::CLAMP_TO_BORDER); - light_grid_tex->setWrap(osg::Texture3D::WRAP_T, osg::Texture3D::CLAMP_TO_BORDER); - light_grid_tex->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::NEAREST); - light_grid_tex->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::NEAREST); - light_grid_tex->setImage(0, _light_grid.get()); - - camera->getOrCreateStateSet()->setTextureAttributeAndModes( - 10, light_grid_tex.get(), osg::StateAttribute::ON); - - // Create and associate the light indices TBO - _light_indices->allocateImage(4096, 1, 1, GL_RED_INTEGER_EXT, GL_UNSIGNED_SHORT); - - osg::ref_ptr light_indices_tbo = - new osg::TextureBuffer; - light_indices_tbo->setInternalFormat(GL_R16UI); - light_indices_tbo->setImage(_light_indices.get()); - - camera->getOrCreateStateSet()->setTextureAttribute( - 11, light_indices_tbo.get()); - - // Create and associate the light data UBO - osg::ref_ptr light_data_ubo = - new osg::UniformBufferObject; - _light_data->setBufferObject(light_data_ubo.get()); - -#if OSG_VERSION_LESS_THAN(3,6,0) - osg::ref_ptr light_data_ubb = - new osg::UniformBufferBinding(0, light_data_ubo.get(), - 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); -#else - osg::ref_ptr light_data_ubb = - new osg::UniformBufferBinding(0, _light_data.get(), - 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); -#endif -light_data_ubb->setDataVariance(osg::Object::DYNAMIC); - - camera->getOrCreateStateSet()->setAttribute( - light_data_ubb.get(), osg::StateAttribute::ON); - - _initialized = true; - } - - std::vector subfrustums; - const osg::Matrix &view_matrix = camera->getViewMatrix(); - const osg::Matrix &proj_matrix = camera->getProjectionMatrix(); - osg::Matrix view_proj_inverse = osg::Matrix::inverse(view_matrix * proj_matrix); - - double x_step = (_tile_size / width) * 2.0; - double y_step = (_tile_size / height) * 2.0; - for (int y = 0; y < n_vtiles; ++y) { - for (int x = 0; x < n_htiles; ++x) { - // Create the subfrustum in clip space - double x_min = -1.0 + x_step * x; double x_max = x_min + x_step; - double y_min = -1.0 + y_step * y; double y_max = y_min + y_step; - double z_min = 1.0; double z_max = -1.0; - osg::BoundingBox subfrustum_bb( - x_min, y_min, z_min, x_max, y_max, z_max); - osg::Polytope subfrustum; - subfrustum.setToBoundingBox(subfrustum_bb); - - // Transform it to world space - subfrustum.transformProvidingInverse(view_proj_inverse); - - subfrustums.push_back(subfrustum); - } - } - - GLushort *grid_data = reinterpret_cast - (_light_grid->data()); - GLushort *index_data = reinterpret_cast - (_light_indices->data()); - - GLushort global_light_count = 0; - for (size_t i = 0; i < subfrustums.size(); ++i) { - GLushort start_offset = global_light_count; - GLushort local_light_count = 0; - - for (GLushort light_list_index = 0; - light_list_index < NUM_LIGHTS; - ++light_list_index) { - const Light &light = LIGHT_LIST[light_list_index]; - osg::BoundingSphere bs(light.position, light.range); - - if (subfrustums[i].contains(bs)) { - index_data[global_light_count] = light_list_index; - ++local_light_count; - ++global_light_count; - } - } - grid_data[i * 3 + 0] = start_offset; - grid_data[i * 3 + 1] = local_light_count; - grid_data[i * 3 + 2] = 0; - } - - _light_grid->dirty(); - _light_indices->dirty(); - - // Upload light data - for (int i = 0; i < DATA_SIZE; ++i) { - (*_light_data)[i] = LIGHT_DATA[i]; - } - - // DEBUG - /* - if (!_debug) { - for (int y = 0; y < num_vtiles; ++y) { - for (int x = 0; x < num_htiles; ++x) { - std::cout << grid_data[(y * num_htiles + x) * 3 + 0] << "," - << grid_data[(y * num_htiles + x) * 3 + 1] << " "; - } - std::cout << std::endl; - } - std::cout << "\n\n"; - - for (int i = 0; i < num_vtiles * num_htiles; ++i) { - std::cout << index_data[i] << " "; - } - std::cout << "\n"; - _debug = true; - } - */ -/* - for (int y = 0; y < num_vtiles; ++y) { - for (int x = 0; x < num_htiles; ++x) { - data[(y * num_htiles + x) * 3 + 0] = (unsigned short)x; - data[(y * num_htiles + x) * 3 + 1] = (unsigned short)y; - data[(y * num_htiles + x) * 3 + 2] = 0; - } - } - _light_grid->dirty(); -*/ -} - -} // namespace compositor -} // namespace simgear diff --git a/simgear/scene/viewer/ClusteredForward.hxx b/simgear/scene/viewer/ClusteredForward.hxx deleted file mode 100644 index 8afa47b0..00000000 --- a/simgear/scene/viewer/ClusteredForward.hxx +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2018 Fernando García Liñán -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Library General Public -// License as published by the Free Software Foundation; either -// version 2 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Library General Public License for more details. -// -// You should have received a copy of the GNU Library General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - -#ifndef SG_CLUSTERED_FORWARD_HXX -#define SG_CLUSTERED_FORWARD_HXX - -#include - -namespace simgear { -namespace compositor { - -class ClusteredForwardDrawCallback : public osg::Camera::DrawCallback { -public: - ClusteredForwardDrawCallback(); - virtual void operator()(osg::RenderInfo &renderInfo) const; -protected: - mutable bool _initialized; - int _tile_size; - osg::ref_ptr _light_grid; - osg::ref_ptr _light_indices; - osg::ref_ptr _light_data; -}; - -} // namespace compositor -} // namespace simgear - -#endif /* SG_CLUSTERED_FORWARD_HXX */ diff --git a/simgear/scene/viewer/ClusteredShading.cxx b/simgear/scene/viewer/ClusteredShading.cxx new file mode 100644 index 00000000..e42883a2 --- /dev/null +++ b/simgear/scene/viewer/ClusteredShading.cxx @@ -0,0 +1,395 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "ClusteredShading.hxx" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace simgear { +namespace compositor { + +const int MAX_LIGHT_INDICES = 524288; // 1 MB (2 bytes per index) +const int MAX_POINTLIGHTS = 256; +const int MAX_SPOTLIGHTS = 256; + +// Size in floats (4 bytes) of the light struct to be passed to the GLSL shader. +// It must be a multiple of the size of a vec4 as per the std140 layout rules. +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt +const int POINTLIGHT_BLOCK_SIZE = 20; +const int SPOTLIGHT_BLOCK_SIZE = 8; + +ClusteredShading::ClusteredShading(osg::Camera *camera, + const SGPropertyNode *config) : + _camera(camera) +{ + _tile_size = config->getIntValue("tile-size", 128); + _depth_slices = config->getIntValue("depth-slices", 1); + _num_threads = config->getIntValue("num-threads", 1); + _slices_per_thread = _depth_slices / _num_threads; + if (_slices_per_thread == 0) { + SG_LOG(SG_INPUT, SG_INFO, "ClusteredShading::ClusteredShading(): " + "More threads than depth slices"); + _num_threads = _depth_slices; + } + _slices_remainder = _depth_slices % _num_threads; + + osg::StateSet *ss = _camera->getOrCreateStateSet(); + + osg::Uniform *tile_size_uniform = + new osg::Uniform("fg_ClusteredTileSize", _tile_size); + ss->addUniform(tile_size_uniform); + _slice_scale = new osg::Uniform("fg_ClusteredSliceScale", 0.0f); + ss->addUniform(_slice_scale.get()); + _slice_bias = new osg::Uniform("fg_ClusteredSliceBias", 0.0f); + ss->addUniform(_slice_bias.get()); + + // Create and associate the light grid 3D texture + //////////////////////////////////////////////////////////////////////////// + _light_grid = new osg::Image; + _light_grid->setInternalTextureFormat(GL_RGB32UI_EXT); + // Image allocation happens in setupSubfrusta() because the light grid size + // can change at runtime (viewport resize) + + osg::ref_ptr light_grid_tex = new osg::Texture3D; + light_grid_tex->setResizeNonPowerOfTwoHint(false); + light_grid_tex->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setWrap(osg::Texture3D::WRAP_S, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setWrap(osg::Texture3D::WRAP_T, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::NEAREST); + light_grid_tex->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::NEAREST); + light_grid_tex->setImage(_light_grid.get()); + + int light_grid_bind_unit = config->getIntValue("grid-bind-unit", 11); + ss->setTextureAttributeAndModes( + light_grid_bind_unit, light_grid_tex.get(), osg::StateAttribute::ON); + + osg::ref_ptr light_grid_uniform = + new osg::Uniform("fg_ClusteredLightGrid", light_grid_bind_unit); + ss->addUniform(light_grid_uniform.get()); + + // Create and associate the light indices TBO + //////////////////////////////////////////////////////////////////////////// + _light_indices = new osg::Image; + _light_indices->allocateImage( + MAX_LIGHT_INDICES, 1, 1, GL_RED_INTEGER_EXT, GL_UNSIGNED_SHORT); + + osg::ref_ptr light_indices_tbo = + new osg::TextureBuffer; + light_indices_tbo->setInternalFormat(GL_R16UI); + light_indices_tbo->setImage(_light_indices.get()); + + int light_indices_bind_unit = config->getIntValue("indices-bind-unit", 12); + ss->setTextureAttribute(light_indices_bind_unit, light_indices_tbo.get()); + + osg::ref_ptr light_indices_uniform = + new osg::Uniform("fg_ClusteredLightIndices", light_indices_bind_unit); + ss->addUniform(light_indices_uniform.get()); + + // Create and associate the pointlight data UBO + //////////////////////////////////////////////////////////////////////////// + _pointlight_data = new osg::FloatArray(MAX_POINTLIGHTS * POINTLIGHT_BLOCK_SIZE); + + osg::ref_ptr pointlight_data_ubo = + new osg::UniformBufferObject; + _pointlight_data->setBufferObject(pointlight_data_ubo.get()); + + int pointlight_ubo_index = config->getIntValue("pointlight-ubo-index", 5); +#if OSG_VERSION_LESS_THAN(3,6,0) + osg::ref_ptr pointlight_data_ubb = + new osg::UniformBufferBinding( + pointlight_ubo_index, + pointlight_data_ubo.get(), + 0, + MAX_POINTLIGHTS * POINTLIGHT_BLOCK_SIZE * sizeof(GLfloat)); +#else + osg::ref_ptr pointlight_data_ubb = + new osg::UniformBufferBinding( + pointlight_ubo_index, + _pointlight_data.get(), + 0, + MAX_POINTLIGHTS * POINTLIGHT_BLOCK_SIZE * sizeof(GLfloat)); +#endif + pointlight_data_ubb->setDataVariance(osg::Object::DYNAMIC); + ss->setAttribute(pointlight_data_ubb.get(), osg::StateAttribute::ON); + + // Create and associate the spotlight data UBO + //////////////////////////////////////////////////////////////////////////// + _spotlight_data = new osg::FloatArray(MAX_SPOTLIGHTS * SPOTLIGHT_BLOCK_SIZE); + + osg::ref_ptr spotlight_data_ubo = + new osg::UniformBufferObject; + _spotlight_data->setBufferObject(spotlight_data_ubo.get()); + + int spotlight_ubo_index = config->getIntValue("spotlight-ubo-index", 6); +#if OSG_VERSION_LESS_THAN(3,6,0) + osg::ref_ptr spotlight_data_ubb = + new osg::UniformBufferBinding( + spotlight_ubo_index, + spotlight_data_ubo.get(), + 0, + MAX_SPOTLIGHTS * SPOTLIGHT_BLOCK_SIZE * sizeof(GLfloat)); +#else + osg::ref_ptr spotlight_data_ubb = + new osg::UniformBufferBinding( + spotlight_ubo_index, + _spotlight_data.get(), + 0, + MAX_SPOTLIGHTS * SPOTLIGHT_BLOCK_SIZE * sizeof(GLfloat)); +#endif + spotlight_data_ubb->setDataVariance(osg::Object::DYNAMIC); + ss->setAttribute(spotlight_data_ubb.get(), osg::StateAttribute::ON); +} + +ClusteredShading::~ClusteredShading() +{ +} + +void +ClusteredShading::update(const SGLightList &light_list) +{ + // Transform every light to a more comfortable data structure for collision + // testing, separating point and spot lights in the process + _point_bounds.clear(); + _spot_bounds.clear(); + for (const auto &light : light_list) { + if (light->getType() == SGLight::Type::POINT) { + PointlightBound point; + point.light = light; + point.position = osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f) * + osg::computeLocalToWorld(light->getParentalNodePaths()[0]) * + _camera->getViewMatrix(); + point.range = light->getRange(); + _point_bounds.push_back(point); + } else if (light->getType() == SGLight::Type::SPOT) { + + } + } + if (_point_bounds.size() > MAX_POINTLIGHTS || + _spot_bounds.size() > MAX_SPOTLIGHTS) { + throw sg_range_exception("Maximum amount of visible lights surpassed"); + } + + float l, r, b, t; + _camera->getProjectionMatrix().getFrustum(l, r, b, t, _zNear, _zFar); + _slice_scale->set(_depth_slices / log2(_zFar / _zNear)); + _slice_bias->set(-_depth_slices * log2(_zNear) / log2(_zFar / _zNear)); + + const osg::Viewport *vp = _camera->getViewport(); + static int old_width = 0, old_height = 0; + int width = vp->width(); int height = vp->height(); + if (width != old_width || height != old_height) { + old_width = width; old_height = height; + + _n_htiles = (width + _tile_size - 1) / _tile_size; + _n_vtiles = (height + _tile_size - 1) / _tile_size; + + _x_step = (_tile_size / float(width)) * 2.0; + _y_step = (_tile_size / float(height)) * 2.0; + + _light_grid->allocateImage(_n_htiles, _n_vtiles, _depth_slices, + GL_RGB_INTEGER_EXT, GL_UNSIGNED_INT); + _subfrusta.reset(new Subfrustum[_n_htiles * _n_vtiles]); + } + + for (int y = 0; y < _n_vtiles; ++y) { + float ymin = -1.0 + _y_step * float(y); + float ymax = ymin + _y_step; + for (int x = 0; x < _n_htiles; ++x) { + float xmin = -1.0 + _x_step * float(x); + float xmax = xmin + _x_step; + + // Create the subfrustum in clip space + // The near and far planes will be filled later as they change from + // slice to slice + Subfrustum &subfrustum = _subfrusta[y*_n_htiles + x]; + subfrustum.plane[0].set(1.0f,0.0f,0.0f,-xmin); // left plane. + subfrustum.plane[1].set(-1.0f,0.0f,0.0f,xmax); // right plane. + subfrustum.plane[2].set(0.0f,1.0f,0.0f,-ymin); // bottom plane. + subfrustum.plane[3].set(0.0f,-1.0f,0.0f,ymax); // top plane. + + // Transform it to view space + for (int i = 0; i < 4; ++i) { + osg::Vec4f &p = subfrustum.plane[i]; + p = _camera->getProjectionMatrix() * p; + float inv_length = 1.0 / sqrt(p._v[0]*p._v[0] + + p._v[1]*p._v[1] + + p._v[2]*p._v[2]); + p *= inv_length; + } + } + } + + _global_light_count = 0; + + if (_depth_slices == 1) { + // Just run the light assignment on the main thread to avoid the + // unnecessary threading overhead + assignLightsToSlice(0); + } else if (_num_threads == 1) { + // Again, avoid the unnecessary threading overhead + threadFunc(0); + } else { + std::vector threads; + threads.reserve(_num_threads); + for (int i = 0; i < _num_threads; ++i) + threads.emplace_back(&ClusteredShading::threadFunc, this, i); + + for (auto &t : threads) t.join(); + } + + // Force upload of the image data + _light_grid->dirty(); + _light_indices->dirty(); + + // Upload pointlight data + writePointlightData(); +} + +void +ClusteredShading::threadFunc(int thread_id) +{ + for (int i = 0; i < _slices_per_thread; ++i) + assignLightsToSlice(thread_id * _slices_per_thread + i); + + if (_slices_remainder > thread_id) + assignLightsToSlice(_slices_per_thread * _num_threads + thread_id); +} + +void +ClusteredShading::assignLightsToSlice(int slice) +{ + size_t z_offset = slice * _n_htiles * _n_vtiles; + + float near = getDepthForSlice(slice); + float far = getDepthForSlice(slice + 1); + osg::Vec4f near_plane(0.0f, 0.0f, -1.0f, -near); + osg::Vec4f far_plane (0.0f, 0.0f, 1.0f, far); + + GLuint *grid = reinterpret_cast(_light_grid->data()); + GLushort *indices = reinterpret_cast(_light_indices->data()); + + for (int i = 0; i < (_n_htiles * _n_vtiles); ++i) { + Subfrustum subfrustum = _subfrusta[i]; + subfrustum.plane[4] = near_plane; + subfrustum.plane[5] = far_plane; + + GLuint start_offset = _global_light_count; + GLuint local_point_count = 0; + GLuint local_spot_count = 0; + + for (GLushort point_iterator = 0; + point_iterator < _point_bounds.size(); + ++point_iterator) { + PointlightBound point = _point_bounds[point_iterator]; + + // Perform frustum-sphere collision tests + float distance = 0.0f; + for (int j = 0; j < 6; j++) { + distance = subfrustum.plane[j] * point.position + point.range; + if (distance <= 0.0f) + break; + } + + if (distance > 0.0f) { + // Update light index list + indices[_global_light_count] = point_iterator; + ++local_point_count; + ++_global_light_count; // Atomic increment + } + + if (_global_light_count >= MAX_LIGHT_INDICES) { + throw sg_range_exception( + "Clustered shading light index count is over the hardcoded limit (" + + std::to_string(MAX_LIGHT_INDICES) + ")"); + } + } + + // Update light grid + grid[(z_offset + i) * 3 + 0] = start_offset; + grid[(z_offset + i) * 3 + 1] = local_point_count; + grid[(z_offset + i) * 3 + 2] = local_spot_count; + } + + // for (int y = 0; y < _n_vtiles; ++y) { + // for (int x = 0; x < _n_htiles; ++x) { + // std::cout << grid[(y * _n_htiles + x) * 3 + 0] << "," + // << grid[(y * _n_htiles + x) * 3 + 1] << " "; + // } + // std::cout << std::endl; + // } + // std::cout << "\n\n"; + + // for (int i = 0; i < n_vtiles * n_htiles; ++i) { + // std::cout << indices[i] << " "; + // } + // std::cout << "\n"; +} + +void +ClusteredShading::writePointlightData() +{ + GLfloat *data = reinterpret_cast(&(*_pointlight_data)[0]); + + for (const auto &point : _point_bounds) { + // vec4 position + *data++ = point.position.x(); + *data++ = point.position.y(); + *data++ = point.position.z(); + *data++ = 1.0f; + // vec4 ambient + *data++ = point.light->getAmbient().x(); + *data++ = point.light->getAmbient().y(); + *data++ = point.light->getAmbient().z(); + *data++ = point.light->getAmbient().w(); + // vec4 diffuse + *data++ = point.light->getDiffuse().x(); + *data++ = point.light->getDiffuse().y(); + *data++ = point.light->getDiffuse().z(); + *data++ = point.light->getDiffuse().w(); + // vec4 specular + *data++ = point.light->getSpecular().x(); + *data++ = point.light->getSpecular().y(); + *data++ = point.light->getSpecular().z(); + *data++ = point.light->getSpecular().w(); + // vec4 attenuation (x = constant, y = linear, z = quadratic, w = range) + *data++ = point.light->getConstantAttenuation(); + *data++ = point.light->getLinearAttenuation(); + *data++ = point.light->getQuadraticAttenuation(); + *data++ = point.light->getRange(); + // No padding needed as the resulting size is a multiple of vec4 + } + _pointlight_data->dirty(); +} + +float +ClusteredShading::getDepthForSlice(int slice) const +{ + return _zNear * pow(_zFar / _zNear, float(slice) / _depth_slices); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/ClusteredShading.hxx b/simgear/scene/viewer/ClusteredShading.hxx new file mode 100644 index 00000000..4843a102 --- /dev/null +++ b/simgear/scene/viewer/ClusteredShading.hxx @@ -0,0 +1,96 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CLUSTERED_SHADING_HXX +#define SG_CLUSTERED_SHADING_HXX + +#include + +#include +#include + +#include + +namespace simgear { +namespace compositor { + +class ClusteredShading : public osg::Referenced { +public: + ClusteredShading(osg::Camera *camera, const SGPropertyNode *config); + ~ClusteredShading(); + + void update(const SGLightList &light_list); +protected: + // We could make use of osg::Polytope, but it does a lot of std::vector + // push_back() calls, so we make our own frustum structure for huge + // performance gains. + struct Subfrustum { + osg::Vec4f plane[6]; + }; + + struct PointlightBound { + SGLight *light; + osg::Vec4f position; + float range; + }; + struct SpotlightBound { + SGLight *light; + osg::Vec4f position; + float range; + }; + + void threadFunc(int thread_id); + void assignLightsToSlice(int slice); + void writePointlightData(); + float getDepthForSlice(int slice) const; + + osg::observer_ptr _camera; + + osg::ref_ptr _slice_scale; + osg::ref_ptr _slice_bias; + + int _tile_size; + int _depth_slices; + int _num_threads; + int _slices_per_thread; + int _slices_remainder; + + float _zNear; + float _zFar; + + int _n_htiles; + int _n_vtiles; + + float _x_step; + float _y_step; + + osg::ref_ptr _light_grid; + osg::ref_ptr _light_indices; + osg::ref_ptr _pointlight_data; + osg::ref_ptr _spotlight_data; + + std::unique_ptr _subfrusta; + + std::vector _point_bounds; + std::vector _spot_bounds; + + std::atomic _global_light_count; +}; + +} // namespace compositor +} // namespace simgear + +#endif /* SG_CLUSTERED_SHADING_HXX */ diff --git a/simgear/scene/viewer/Compositor.cxx b/simgear/scene/viewer/Compositor.cxx index f1caf50c..964e387d 100644 --- a/simgear/scene/viewer/Compositor.cxx +++ b/simgear/scene/viewer/Compositor.cxx @@ -39,7 +39,8 @@ Compositor * Compositor::create(osg::View *view, osg::GraphicsContext *gc, osg::Viewport *viewport, - const SGPropertyNode *property_list) + const SGPropertyNode *property_list, + const SGReaderWriterOptions *options) { osg::ref_ptr compositor = new Compositor(view, gc, viewport); compositor->_name = property_list->getStringValue("name"); @@ -55,7 +56,7 @@ Compositor::create(osg::View *view, "a name to be available to passes. Skipping..."); continue; } - Buffer *buffer = buildBuffer(compositor.get(), p_buffer); + Buffer *buffer = buildBuffer(compositor.get(), p_buffer, options); if (buffer) compositor->addBuffer(buffer_name, buffer); } @@ -64,7 +65,7 @@ Compositor::create(osg::View *view, for (auto const &p_pass : p_passes) { if (!checkConditional(p_pass)) continue; - Pass *pass = buildPass(compositor.get(), p_pass); + Pass *pass = buildPass(compositor.get(), p_pass, options); if (pass) compositor->addPass(pass); } @@ -76,7 +77,8 @@ Compositor * Compositor::create(osg::View *view, osg::GraphicsContext *gc, osg::Viewport *viewport, - const std::string &name) + const std::string &name, + const SGReaderWriterOptions *options) { std::string filename(name); filename += ".xml"; @@ -96,7 +98,7 @@ Compositor::create(osg::View *view, return 0; } - return create(view, gc, viewport, property_list); + return create(view, gc, viewport, property_list, options); } Compositor::Compositor(osg::View *view, @@ -200,51 +202,6 @@ Compositor::resized() } } -bool -Compositor::computeIntersection( - const osg::Vec2d& windowPos, - osgUtil::LineSegmentIntersector::Intersections& intersections) -{ - using osgUtil::Intersector; - using osgUtil::LineSegmentIntersector; - - osg::Camera *camera = getPass(0)->camera; - const osg::Viewport* viewport = camera->getViewport(); - SGRect viewportRect(viewport->x(), viewport->y(), - viewport->x() + viewport->width() - 1.0, - viewport->y() + viewport->height()- 1.0); - - double epsilon = 0.5; - if (!viewportRect.contains(windowPos.x(), windowPos.y(), epsilon)) - return false; - - osg::Vec4d start(windowPos.x(), windowPos.y(), 0.0, 1.0); - osg::Vec4d end(windowPos.x(), windowPos.y(), 1.0, 1.0); - osg::Matrix windowMat = viewport->computeWindowMatrix(); - osg::Matrix startPtMat = osg::Matrix::inverse(camera->getProjectionMatrix() - * windowMat); - osg::Matrix endPtMat = startPtMat; // no far camera - - start = start * startPtMat; - start /= start.w(); - end = end * endPtMat; - end /= end.w(); - osg::ref_ptr picker - = new LineSegmentIntersector(Intersector::VIEW, - osg::Vec3d(start.x(), start.y(), start.z()), - osg::Vec3d(end.x(), end.y(), end.z())); - osgUtil::IntersectionVisitor iv(picker.get()); - iv.setTraversalMask( simgear::PICK_BIT ); - - const_cast(camera)->accept(iv); - if (picker->containsIntersections()) { - intersections = picker->getIntersections(); - return true; - } - - return false; -} - void Compositor::addBuffer(const std::string &name, Buffer *buffer) { @@ -272,7 +229,7 @@ Compositor::addPass(Pass *pass) identifier = sceneView->getCullVisitor()->getIdentifier(); sceneView->setCullVisitor( - new EffectCullVisitor(false, pass->effect_override)); + new EffectCullVisitor(false, pass->effect_scheme)); sceneView->getCullVisitor()->setIdentifier(identifier.get()); identifier = sceneView->getCullVisitorLeft()->getIdentifier(); diff --git a/simgear/scene/viewer/Compositor.hxx b/simgear/scene/viewer/Compositor.hxx index c1761ef1..f2d0cc75 100644 --- a/simgear/scene/viewer/Compositor.hxx +++ b/simgear/scene/viewer/Compositor.hxx @@ -73,7 +73,8 @@ public: static Compositor *create(osg::View *view, osg::GraphicsContext *gc, osg::Viewport *viewport, - const SGPropertyNode *property_list); + const SGPropertyNode *property_list, + const SGReaderWriterOptions *options); /** * \overload * \brief Create a Compositor from a file. @@ -84,20 +85,19 @@ public: static Compositor *create(osg::View *view, osg::GraphicsContext *gc, osg::Viewport *viewport, - const std::string &name); + const std::string &name, + const SGReaderWriterOptions *options); void update(const osg::Matrix &view_matrix, const osg::Matrix &proj_matrix); void resized(); - bool computeIntersection( - const osg::Vec2d& windowPos, - osgUtil::LineSegmentIntersector::Intersections& intersections); + osg::View *getView() const { return _view; } - const osg::GraphicsContext *getGraphicsContext() const { return _gc; } + osg::GraphicsContext *getGraphicsContext() const { return _gc; } - const osg::Viewport *getViewport() const { return _viewport; } + osg::Viewport *getViewport() const { return _viewport; } typedef std::array< osg::ref_ptr, @@ -121,8 +121,6 @@ public: Pass * getPass(const std::string &name) const; protected: - friend class PassBuilder; - osg::View *_view; osg::GraphicsContext *_gc; osg::ref_ptr _viewport; diff --git a/simgear/scene/viewer/CompositorBuffer.cxx b/simgear/scene/viewer/CompositorBuffer.cxx index 2acb6be9..80071ef4 100644 --- a/simgear/scene/viewer/CompositorBuffer.cxx +++ b/simgear/scene/viewer/CompositorBuffer.cxx @@ -28,6 +28,7 @@ #include #include #include +#include #include "Compositor.hxx" #include "CompositorUtil.hxx" @@ -49,6 +50,7 @@ PropStringMap buffer_format_map { {"rgba16f", {GL_RGBA16F_ARB, GL_RGBA, GL_FLOAT}}, {"rgba32f", {GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT}}, {"r32f", {GL_R32F, GL_RED, GL_FLOAT}}, + {"rg16f", {GL_RG16F, GL_RG, 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}}, @@ -92,7 +94,8 @@ PropStringMap shadow_compare_func_map = { }; Buffer * -buildBuffer(Compositor *compositor, const SGPropertyNode *node) +buildBuffer(Compositor *compositor, const SGPropertyNode *node, + const SGReaderWriterOptions *options) { std::string type = node->getStringValue("type"); if (type.empty()) { diff --git a/simgear/scene/viewer/CompositorBuffer.hxx b/simgear/scene/viewer/CompositorBuffer.hxx index df0a92a4..92a9d5e0 100644 --- a/simgear/scene/viewer/CompositorBuffer.hxx +++ b/simgear/scene/viewer/CompositorBuffer.hxx @@ -22,6 +22,9 @@ class SGPropertyNode; namespace simgear { + +class SGReaderWriterOptions; + namespace compositor { class Compositor; @@ -38,7 +41,8 @@ struct Buffer : public osg::Referenced { float width_scale, height_scale; }; -Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node); +Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node, + const SGReaderWriterOptions *options); } // namespace compositor } // namespace simgear diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx index 8ee399d7..7e82765d 100644 --- a/simgear/scene/viewer/CompositorPass.cxx +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -22,13 +22,17 @@ #include #include +#include + #include +#include #include #include +#include #include #include -#include "ClusteredForward.hxx" +#include "ClusteredShading.hxx" #include "Compositor.hxx" #include "CompositorUtil.hxx" @@ -50,34 +54,37 @@ PropStringMap buffer_component_map = { {"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(); +Pass * +PassBuilder::build(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options) +{ osg::ref_ptr pass = new Pass; + // The pass index matches its render order + pass->render_order = root->getIndex(); pass->name = root->getStringValue("name"); if (pass->name.empty()) { - SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << render_order + SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << 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); + pass->effect_scheme = root->getStringValue("effect-scheme"); osg::Camera *camera = new Camera; pass->camera = camera; camera->setName(pass->name); - camera->setGraphicsContext(compositor->_gc); + camera->setGraphicsContext(compositor->getGraphicsContext()); // 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); + // We only use POST_RENDER. Leave PRE_RENDER for Canvas and other RTT stuff + // that doesn't involve the rendering pipeline itself. NESTED_RENDER is also + // not a possibility since we don't want to share RenderStage with the View + // master camera. + camera->setRenderOrder(osg::Camera::POST_RENDER, pass->render_order * 10); camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); // XXX: Should we make this configurable? @@ -144,7 +151,7 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) 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 + << p_binding->getIndex() << " in pass " << pass->render_order << ": " << e.what()); } } @@ -153,22 +160,15 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) 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); + camera->setViewport(compositor->getViewport()); } 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; @@ -252,13 +252,13 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) camera->setViewport( 0, 0, - buffer->width_scale * compositor->_viewport->width(), - buffer->height_scale * compositor->_viewport->height()); + buffer->width_scale * compositor->getViewport()->width(), + buffer->height_scale * compositor->getViewport()->height()); } } } catch (sg_exception &e) { SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping attachment " - << p_attachment->getIndex() << " in pass " << render_order + << p_attachment->getIndex() << " in pass " << pass->render_order << ": " << e.what()); } } @@ -271,8 +271,10 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) struct QuadPassBuilder : public PassBuilder { public: - virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { - osg::ref_ptr pass = PassBuilder::build(compositor, root); + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options) { + osg::ref_ptr pass = PassBuilder::build(compositor, root, options); + pass->useMastersSceneData = false; osg::Camera *camera = pass->camera; camera->setAllowEventFocus(false); @@ -289,11 +291,29 @@ public: scale = p_geometry->getFloatValue("scale", scale); } - const std::string eff_file = root->getStringValue("effect"); - - osg::ref_ptr quad = createFullscreenQuad( - left, bottom, width, height, scale, eff_file); + osg::ref_ptr quad = new EffectGeode; camera->addChild(quad); + quad->setCullingActive(false); + + const std::string eff_file = root->getStringValue("effect"); + if (!eff_file.empty()) { + Effect *eff = makeEffect(eff_file, true, options); + if (eff) + quad->setEffect(eff); + } + + osg::ref_ptr geom = createFullscreenQuadGeom( + left, bottom, width, height, scale); + quad->addDrawable(geom); + + osg::ref_ptr quad_state = quad->getOrCreateStateSet(); + int values = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; + quad_state->setAttribute(new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, + osg::PolygonMode::FILL), + values); + quad_state->setMode(GL_LIGHTING, values); + quad_state->setMode(GL_DEPTH_TEST, values); osg::StateSet *ss = camera->getOrCreateStateSet(); for (const auto &uniform : compositor->getUniforms()) @@ -302,13 +322,12 @@ public: 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; + osg::Geometry *createFullscreenQuadGeom(float left, + float bottom, + float width, + float height, + float scale) { + osg::ref_ptr geom; // When the quad is fullscreen, it can be optimized by using a // a fullscreen triangle instead of a quad to avoid discarding pixels @@ -349,31 +368,52 @@ protected: osg::PrimitiveSet::TRIANGLES, 0, 3)); } - osg::ref_ptr quad = new EffectGeode; - if (!eff_file.empty()) { - Effect *eff = makeEffect(eff_file, true, 0); - if (eff) - quad->setEffect(eff); - } - quad->addDrawable(geom); - quad->setCullingActive(false); - - osg::ref_ptr quad_state = quad->getOrCreateStateSet(); - int values = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; - quad_state->setAttribute(new osg::PolygonMode( - osg::PolygonMode::FRONT_AND_BACK, - osg::PolygonMode::FILL), - values); - quad_state->setMode(GL_LIGHTING, values); - quad_state->setMode(GL_DEPTH_TEST, values); - - return quad.release(); + return geom.release(); } }; RegisterPassBuilder registerQuadPass("quad"); //------------------------------------------------------------------------------ +class ShadowMapCullCallback : public osg::NodeCallback { +public: + ShadowMapCullCallback(const std::string &suffix) { + _light_matrix_uniform = new osg::Uniform( + osg::Uniform::FLOAT_MAT4, std::string("fg_LightMatrix_") + suffix); + } + + virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) { + osg::Camera *camera = static_cast(node); + + traverse(node, nv); + + // The light matrix uniform is updated after the traverse in case the + // OSG near/far plane calculations were enabled + osg::Matrixf light_matrix = + // Include the real camera inverse view matrix because if the shader + // used world coordinates, there would be precision issues. + _real_inverse_view * + 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); + } + + void setRealInverseViewMatrix(const osg::Matrix &matrix) { + _real_inverse_view = matrix; + } + + osg::Uniform *getLightMatrixUniform() const { + return _light_matrix_uniform.get(); + } + +protected: + osg::Matrix _real_inverse_view; + osg::ref_ptr _light_matrix_uniform; +}; + class LightFinder : public osg::NodeVisitor { public: LightFinder(const std::string &name) : @@ -406,15 +446,14 @@ protected: struct ShadowMapUpdateCallback : public Pass::PassUpdateCallback { public: - ShadowMapUpdateCallback(const std::string &light_name, + ShadowMapUpdateCallback(ShadowMapCullCallback *cull_callback, + const std::string &light_name, float near_m, float far_m, - const std::string &suffix, int sm_width, int sm_height) : + _cull_callback(cull_callback), _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, @@ -443,6 +482,7 @@ public: // This is not a problem though (for now). osg::Matrix view_inverse = osg::Matrix::inverse(view_matrix); + _cull_callback->setRealInverseViewMatrix(view_inverse); // Calculate the light's point of view transformation matrices. // Taken from Project Rembrandt. @@ -489,37 +529,30 @@ public: 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::observer_ptr _cull_callback; osg::ref_ptr _light_finder; float _near_m; float _far_m; - osg::ref_ptr _light_matrix_uniform; osg::Vec2d _half_sm_size; }; struct ShadowMapPassBuilder : public PassBuilder { - virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { - osg::ref_ptr pass = PassBuilder::build(compositor, root); - pass->useMastersSceneData = true; + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options) { + osg::ref_ptr pass = PassBuilder::build(compositor, root, options); osg::Camera *camera = pass->camera; camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + camera->setCullingMode(camera->getCullingMode() & + ~osg::CullSettings::SMALL_FEATURE_CULLING); + //camera->setComputeNearFarMode( + // osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + + ShadowMapCullCallback *cull_callback = new ShadowMapCullCallback(pass->name); + camera->setCullCallback(cull_callback); std::string light_name = root->getStringValue("light-name"); float near_m = root->getFloatValue("near-m"); @@ -527,9 +560,9 @@ struct ShadowMapPassBuilder : public PassBuilder { int sm_width = camera->getViewport()->width(); int sm_height = camera->getViewport()->height(); pass->update_callback = new ShadowMapUpdateCallback( + cull_callback, light_name, near_m, far_m, - pass->name, sm_width, sm_height); return pass.release(); @@ -624,43 +657,69 @@ protected: float _zFar; }; +class SceneCullCallback : public osg::NodeCallback { +public: + SceneCullCallback(ClusteredShading *clustered) : + _clustered(clustered) {} + + virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) { + osg::Camera *camera = static_cast(node); + EffectCullVisitor *cv = dynamic_cast(nv); + + cv->traverse(*camera); + + if (_clustered) { + // Retrieve the light list from the cull visitor + SGLightList light_list = cv->getLightList(); + _clustered->update(light_list); + } + } +protected: + osg::ref_ptr _clustered; +}; + struct ScenePassBuilder : public PassBuilder { public: - virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { - osg::ref_ptr pass = PassBuilder::build(compositor, root); - pass->useMastersSceneData = true; + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options) { + osg::ref_ptr pass = PassBuilder::build(compositor, root, options); 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); - } + const SGPropertyNode *p_clustered = root->getNode("clustered-shading"); + ClusteredShading *clustered = 0; + if (p_clustered) + clustered = new ClusteredShading(camera, p_clustered); + + camera->setCullCallback(new SceneCullCallback(clustered)); int cubemap_face = root->getIntValue("cubemap-face", -1); float zNear = root->getFloatValue("z-near", 0.0f); float zFar = root->getFloatValue("z-far", 0.0f); pass->update_callback = new SceneUpdateCallback(cubemap_face, zNear, zFar); - std::string shadow_pass_name = root->getStringValue("use-shadow-pass"); - if (!shadow_pass_name.empty()) { - Pass *shadow_pass = compositor->getPass(shadow_pass_name); - if (shadow_pass) { - ShadowMapUpdateCallback *updatecb = - dynamic_cast( - shadow_pass->update_callback.get()); - if (updatecb) { - camera->getOrCreateStateSet()->addUniform( - updatecb->getLightMatrixUniform()); + PropertyList p_shadow_passes = root->getChildren("use-shadow-pass"); + for (const auto &p_shadow_pass : p_shadow_passes) { + std::string shadow_pass_name = p_shadow_pass->getStringValue(); + if (!shadow_pass_name.empty()) { + Pass *shadow_pass = compositor->getPass(shadow_pass_name); + if (shadow_pass) { + ShadowMapCullCallback *cullcb = + dynamic_cast( + shadow_pass->camera->getCullCallback()); + if (cullcb) { + camera->getOrCreateStateSet()->addUniform( + cullcb->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: Pass '" - << shadow_pass_name << "is not a shadow pass"); + SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not " + "find shadow pass named '" << shadow_pass_name << "'"); } - } else { - SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not " - "find shadow pass named '" << shadow_pass_name << "'"); } } @@ -673,7 +732,8 @@ RegisterPassBuilder registerScenePass("scene"); //------------------------------------------------------------------------------ Pass * -buildPass(Compositor *compositor, const SGPropertyNode *root) +buildPass(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options) { std::string type = root->getStringValue("type"); if (type.empty()) { @@ -687,7 +747,7 @@ buildPass(Compositor *compositor, const SGPropertyNode *root) return 0; } - return builder->build(compositor, root); + return builder->build(compositor, root, options); } } // namespace compositor diff --git a/simgear/scene/viewer/CompositorPass.hxx b/simgear/scene/viewer/CompositorPass.hxx index 68fe89e3..d8e26fa8 100644 --- a/simgear/scene/viewer/CompositorPass.hxx +++ b/simgear/scene/viewer/CompositorPass.hxx @@ -27,6 +27,9 @@ #include namespace simgear { + +class SGReaderWriterOptions; + namespace compositor { class Compositor; @@ -45,17 +48,17 @@ class Compositor; */ struct Pass : public osg::Referenced { Pass() : - useMastersSceneData(false), + useMastersSceneData(true), cull_mask(0xffffff), inherit_cull_mask(false), viewport_width_scale(0.0f), viewport_height_scale(0.0f) {} + int render_order; std::string name; std::string type; + std::string effect_scheme; osg::ref_ptr camera; - /** If null, there is no effect override for this pass. */ - osg::ref_ptr effect_override; bool useMastersSceneData; osg::Node::NodeMask cull_mask; /** Whether the cull mask is ANDed with the view master camera cull mask. */ @@ -85,10 +88,11 @@ public: * and overrided for more special passes. * * @param compositor The Compositor instance that owns the pass. - * @param The root node of the pass property tree. + * @param root 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); + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options); static PassBuilder *find(const std::string &type) { auto itr = PassBuilderMapSingleton::instance()->_map.find(type); @@ -125,7 +129,8 @@ struct RegisterPassBuilder { * @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); +Pass *buildPass(Compositor *compositor, const SGPropertyNode *root, + const SGReaderWriterOptions *options); } // namespace compositor } // namespace simgear diff --git a/simgear/sound/CMakeLists.txt b/simgear/sound/CMakeLists.txt index b284d736..884e9520 100644 --- a/simgear/sound/CMakeLists.txt +++ b/simgear/sound/CMakeLists.txt @@ -6,6 +6,7 @@ set(HEADERS xmlsound.hxx soundmgr.hxx filters.hxx + readwav.hxx ) set(SOURCES @@ -13,6 +14,7 @@ set(SOURCES sample_group.cxx xmlsound.cxx filters.cxx + readwav.cxx ) if (USE_AEONWAVE) @@ -20,13 +22,9 @@ if (USE_AEONWAVE) soundmgr_aeonwave.cxx ) else() - set(HEADERS ${HEADERS} - readwav.hxx - ) set(SOURCES ${SOURCES} soundmgr_openal.cxx soundmgr_openal_private.hxx - readwav.cxx ) endif() diff --git a/simgear/sound/soundmgr.hxx b/simgear/sound/soundmgr.hxx index 89b8cda4..6837a35f 100644 --- a/simgear/sound/soundmgr.hxx +++ b/simgear/sound/soundmgr.hxx @@ -49,19 +49,21 @@ class SGSoundSample; class SGSoundMgr : public SGSubsystem { public: - SGSoundMgr(); ~SGSoundMgr(); - void init(); - void update(double dt); + // Subsystem API. + void init() override; + void reinit() override; + void resume() override; + void suspend() override; + void update(double dt) override; + + // Subsystem identification. + static const char* staticSubsystemClassId() { return "sound"; } - void suspend(); - void resume(); void stop(); - void reinit(); - /** * Select a specific sound device. * Requires a init/reinit call before sound is actually switched. @@ -310,7 +312,7 @@ public: size_t *size, int *freq, int *block ); - + /** * Get a list of available playback devices. */ @@ -324,7 +326,6 @@ public: bool testForError(std::string s, std::string name = "sound manager"); - static const char* subsystemName() { return "sound"; }; private: class SoundManagerPrivate; /// private implementation object diff --git a/simgear/sound/soundmgr_openal.cxx b/simgear/sound/soundmgr_openal.cxx index cc3b420a..8b91652d 100644 --- a/simgear/sound/soundmgr_openal.cxx +++ b/simgear/sound/soundmgr_openal.cxx @@ -340,7 +340,7 @@ void SGSoundMgr::suspend() for ( auto current = d->_sample_groups.begin(); current != d->_sample_groups.end(); ++current ) { SGSampleGroup *sgrp = current->second; - sgrp->stop(); + sgrp->suspend(); } _active = false; } diff --git a/simgear/sound/xmlsound.cxx b/simgear/sound/xmlsound.cxx index 7bb25c89..148c6b39 100644 --- a/simgear/sound/xmlsound.cxx +++ b/simgear/sound/xmlsound.cxx @@ -83,6 +83,9 @@ SGXmlSound::~SGXmlSound() if (_sample) _sample->stop(); + if (_sgrp && (_name != "")) + _sgrp->remove(_name); + _volume.clear(); _pitch.clear(); } diff --git a/simgear/structure/SGExpression.cxx b/simgear/structure/SGExpression.cxx index 2633e311..e6b38500 100644 --- a/simgear/structure/SGExpression.cxx +++ b/simgear/structure/SGExpression.cxx @@ -33,6 +33,7 @@ #include #include #include // for strcmp +#include #include diff --git a/simgear/structure/SGPerfMon.cxx b/simgear/structure/SGPerfMon.cxx index c4d7caed..da754cab 100644 --- a/simgear/structure/SGPerfMon.cxx +++ b/simgear/structure/SGPerfMon.cxx @@ -138,3 +138,7 @@ SGPerformanceMonitor::reportTiming(const string& name, SampleStatistic* timeStat timeStat->reset(); } + + +// Register the subsystem. +//SGSubsystemMgr::Registrant registrantSGPerformanceMonitor; diff --git a/simgear/structure/SGPerfMon.hxx b/simgear/structure/SGPerfMon.hxx index 32d642cc..63354d4a 100644 --- a/simgear/structure/SGPerfMon.hxx +++ b/simgear/structure/SGPerfMon.hxx @@ -34,14 +34,17 @@ class SampleStatistic; class SGPerformanceMonitor : public SGSubsystem { - public: SGPerformanceMonitor(SGSubsystemMgr* subSysMgr, SGPropertyNode_ptr root); - virtual void bind (void); - virtual void unbind (void); - virtual void init (void); - virtual void update (double dt); + // Subsystem API. + void bind() override; + void init() override; + void unbind() override; + void update(double dt) override; + + // Subsystem identification. + static const char* staticSubsystemClassId() { return "performance-mon"; } private: static void subSystemMgrHook(void* userData, const std::string& name, SampleStatistic* timeStat); diff --git a/simgear/structure/event_mgr.cxx b/simgear/structure/event_mgr.cxx index dead48b7..2160d6c3 100644 --- a/simgear/structure/event_mgr.cxx +++ b/simgear/structure/event_mgr.cxx @@ -47,7 +47,6 @@ SGEventMgr::SGEventMgr() : _inited(false), _shutdown(false) { -_name = "EventMgr"; } SGEventMgr::~SGEventMgr() @@ -127,6 +126,11 @@ void SGEventMgr::dump() _rtQueue.dump(); } +// Register the subsystem. +SGSubsystemMgr::Registrant registrantSGEventMgr( + SGSubsystemMgr::DISPLAY); + + //////////////////////////////////////////////////////////////////////// // SGTimerQueue // This is the priority queue implementation: diff --git a/simgear/structure/event_mgr.hxx b/simgear/structure/event_mgr.hxx index 70d516fb..9873cf71 100644 --- a/simgear/structure/event_mgr.hxx +++ b/simgear/structure/event_mgr.hxx @@ -8,11 +8,12 @@ class SGEventMgr; -class SGTimer { +class SGTimer +{ public: ~SGTimer(); void run(); - + std::string name; double interval; SGCallback* callback; @@ -20,7 +21,8 @@ public: bool running; }; -class SGTimerQueue { +class SGTimerQueue +{ public: SGTimerQueue(int preSize=1); ~SGTimerQueue(); @@ -37,8 +39,9 @@ public: double nextTime() { return -_table[0].pri; } SGTimer* findByName(const std::string& name) const; - + void dump(); + private: // The "priority" is stored as a negative time. This allows the // implementation to treat the "top" of the heap as the largest @@ -50,9 +53,9 @@ private: int rchild(int n) { return ((n+1)*2 + 1) - 1; } double pri(int n) { return _table[n].pri; } void swap(int a, int b) { - HeapEntry tmp = _table[a]; - _table[a] = _table[b]; - _table[b] = tmp; + HeapEntry tmp = _table[a]; + _table[a] = _table[b]; + _table[b] = tmp; } void siftDown(int n); void siftUp(int n); @@ -73,10 +76,15 @@ public: SGEventMgr(); ~SGEventMgr(); - virtual void init(); - virtual void update(double delta_time_sec); - virtual void unbind(); - virtual void shutdown(); + // Subsystem API. + void init() override; + void shutdown() override; + void unbind() override; + void update(double delta_time_sec) override; + + // Subsystem identification. + static const char* staticSubsystemClassId() { return "events"; } + void setRealtimeProperty(SGPropertyNode* node) { _rtProp = node; } /** @@ -119,8 +127,9 @@ public: void removeTask(const std::string& name); - + void dump(); + private: friend class SGTimer; @@ -130,7 +139,7 @@ private: SGPropertyNode_ptr _freezeProp; SGPropertyNode_ptr _rtProp; - SGTimerQueue _rtQueue; + SGTimerQueue _rtQueue; SGTimerQueue _simQueue; bool _inited, _shutdown; }; diff --git a/simgear/structure/subsystem_mgr.cxx b/simgear/structure/subsystem_mgr.cxx index bd990546..6ceff02e 100644 --- a/simgear/structure/subsystem_mgr.cxx +++ b/simgear/structure/subsystem_mgr.cxx @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -137,28 +138,28 @@ void SGSubsystem::stamp(const string& name) void SGSubsystem::set_name(const std::string &n) { - assert(_name.empty()); - _name = n; + assert(_subsystemId.empty()); + _subsystemId = n; } -std::string SGSubsystem::typeName() const +std::string SGSubsystem::subsystemClassId() const { - auto pos = _name.find(SUBSYSTEM_NAME_SEPARATOR); + auto pos = _subsystemId.find(SUBSYSTEM_NAME_SEPARATOR); if (pos == std::string::npos) { - return _name; + return _subsystemId; } - return _name.substr(0, pos); + return _subsystemId.substr(0, pos); } -std::string SGSubsystem::instanceName() const +std::string SGSubsystem::subsystemInstanceId() const { - auto pos = _name.find(SUBSYSTEM_NAME_SEPARATOR); + auto pos = _subsystemId.find(SUBSYSTEM_NAME_SEPARATOR); if (pos == std::string::npos) { return {}; } - return _name.substr(pos+1); + return _subsystemId.substr(pos+1); } void SGSubsystem::set_group(SGSubsystemGroup* group) @@ -174,7 +175,7 @@ SGSubsystemGroup* SGSubsystem::get_group() const SGSubsystemMgr* SGSubsystem::get_manager() const { if (!get_group()) - throw sg_exception("SGSubsystem::get_manager: subsystem " + name() + " has no group"); + throw sg_exception("SGSubsystem::get_manager: subsystem " + subsystemId() + " has no group"); return get_group()->get_manager(); } @@ -232,12 +233,11 @@ public: -SGSubsystemGroup::SGSubsystemGroup(const char *name) : +SGSubsystemGroup::SGSubsystemGroup() : _fixedUpdateTime(-1.0), _updateTimeRemainder(0.0), _initPosition(-1) { - _name = name; } SGSubsystemGroup::~SGSubsystemGroup () @@ -473,7 +473,7 @@ void SGSubsystem::reportTimingStats(TimerStats *__lastValues) { if (reportDeltas) { auto deltaT = _executionTime - _lastExecutionTime; if (deltaT != 0) { - t << name() << "(+" << std::setprecision(2) << std::right << deltaT << "ms)."; + t << subsystemInstanceId() << "(+" << std::setprecision(2) << std::right << deltaT << "ms)."; _name = t.str(); } } @@ -505,7 +505,7 @@ void SGSubsystem::reportTimingStats(TimerStats *__lastValues) { void SGSubsystemGroup::reportTimingStats(TimerStats *_lastValues) { SGSubsystem::reportTimingStats(_lastValues); - std::string _name = name(); + std::string _name = subsystemInstanceId(); if (!_name.size()) _name = typeid(this).name(); if (_lastValues) { @@ -513,11 +513,11 @@ void SGSubsystemGroup::reportTimingStats(TimerStats *_lastValues) { if (deltaT != 0) { SG_LOG(SG_EVENT, SG_ALERT, " +" << std::setw(6) << std::setprecision(4) << std::right << deltaT << "ms " - << name() ); + << subsystemInstanceId() ); } } else - SG_LOG(SG_EVENT, SG_ALERT, "SubSystemGroup: " << name() << " " << std::setw(6) << std::setprecision(4) << std::right << _executionTime / 1000.0 << "s"); + SG_LOG(SG_EVENT, SG_ALERT, "SubSystemGroup: " << subsystemInstanceId() << " " << std::setw(6) << std::setprecision(4) << std::right << _executionTime / 1000.0 << "s"); for (auto member : _members) { member->reportTimingStats(_lastValues); } @@ -574,11 +574,11 @@ SGSubsystemGroup::set_subsystem (const string &name, SGSubsystem * subsystem, if (name.empty()) { SG_LOG(SG_GENERAL, SG_DEV_WARN, "adding subsystem to group without a name"); // TODO, make this an exception in the future - } else if (subsystem->name().empty()) { + } else if (subsystem->subsystemId().empty()) { subsystem->set_name(name); - } else if (name != subsystem->name()) { + } else if (name != subsystem->subsystemId()) { SG_LOG(SG_GENERAL, SG_DEV_WARN, "adding subsystem to group with name '" << name - << "', but name() returns '" << subsystem->name() << "'"); + << "', but subsystemId() returns '" << subsystem->subsystemId() << "'"); } notifyWillChange(subsystem, State::ADD); @@ -614,7 +614,7 @@ SGSubsystemGroup::set_subsystem (const string &name, SGSubsystem * subsystem, void SGSubsystemGroup::set_subsystem (SGSubsystem * subsystem, double min_step_sec) { - set_subsystem(subsystem->name(), subsystem, min_step_sec); + set_subsystem(subsystem->subsystemId(), subsystem, min_step_sec); } SGSubsystem * @@ -837,7 +837,7 @@ namespace { } // end of anonymous namespace -SGSubsystemMgr::SGSubsystemMgr (const char *name) : +SGSubsystemMgr::SGSubsystemMgr () : _groups(MAX_GROUPS) { if (global_defaultSubsystemManager == nullptr) { @@ -854,7 +854,7 @@ SGSubsystemMgr::SGSubsystemMgr (const char *name) : #endif for (int i = 0; i < MAX_GROUPS; i++) { - auto g = new SGSubsystemGroup(name); + auto g = new SGSubsystemGroup(); g->set_manager(this); _groups[i].reset(g); } @@ -1038,9 +1038,9 @@ SGSubsystemMgr::get_subsystem (const string &name) const } SGSubsystem* -SGSubsystemMgr::get_subsystem(const std::string &name, const std::string& instanceName) const +SGSubsystemMgr::get_subsystem(const std::string &name, const std::string& subsystemInstanceId) const { - return get_subsystem(name + SUBSYSTEM_NAME_SEPARATOR + instanceName); + return get_subsystem(name + SUBSYSTEM_NAME_SEPARATOR + subsystemInstanceId); } @@ -1173,7 +1173,7 @@ SGSubsystemMgr::create(const std::string& name) } SGSubsystemRef -SGSubsystemMgr::createInstance(const std::string& name, const std::string& instanceName) +SGSubsystemMgr::createInstance(const std::string& name, const std::string& subsystemInstanceId) { auto it = findRegistration(name); auto &global_registrations = getGlobalRegistrations(); @@ -1190,7 +1190,7 @@ SGSubsystemMgr::createInstance(const std::string& name, const std::string& insta throw sg_exception("SGSubsystemMgr::create: functor failed to create an instsance of " + name); } - const auto combinedName = name + SUBSYSTEM_NAME_SEPARATOR + instanceName; + const auto combinedName = name + SUBSYSTEM_NAME_SEPARATOR + subsystemInstanceId; ref->set_name(combinedName); return ref; } @@ -1275,7 +1275,7 @@ namespace { // allow override of the name but defaultt o the subsystem name std::string name = arg->getStringValue("name"); - std::string instanceName = arg->getStringValue("instance"); + std::string subsystemInstanceId = arg->getStringValue("instance"); if (name.empty()) { // default name to subsystem name, but before we parse any instance name @@ -1284,19 +1284,19 @@ namespace { auto separatorPos = subsystem.find(SUBSYSTEM_NAME_SEPARATOR); if (separatorPos != std::string::npos) { - if (!instanceName.empty()) { + if (!subsystemInstanceId.empty()) { SG_LOG(SG_GENERAL, SG_WARN, "Specified a composite subsystem name and an instance name, please do one or the other: " - << instanceName << " and " << subsystem); + << subsystemInstanceId << " and " << subsystem); return false; } - instanceName = subsystem.substr(separatorPos + 1); + subsystemInstanceId = subsystem.substr(separatorPos + 1); subsystem = subsystem.substr(0, separatorPos); } SGSubsystem* sub = nullptr; - if (!instanceName.empty()) { - sub = manager->createInstance(subsystem, instanceName); + if (!subsystemInstanceId.empty()) { + sub = manager->createInstance(subsystem, subsystemInstanceId); } else { sub = manager->create(subsystem); } @@ -1312,7 +1312,7 @@ namespace { double minTime = arg->getDoubleValue("min-time-sec", 0.0); - const auto combinedName = subsystem + SUBSYSTEM_NAME_SEPARATOR + instanceName; + const auto combinedName = subsystem + SUBSYSTEM_NAME_SEPARATOR + subsystemInstanceId; manager->add(combinedName.c_str(), sub, group, diff --git a/simgear/structure/subsystem_mgr.hxx b/simgear/structure/subsystem_mgr.hxx index b991a9ef..a8e99f69 100644 --- a/simgear/structure/subsystem_mgr.hxx +++ b/simgear/structure/subsystem_mgr.hxx @@ -127,6 +127,7 @@ typedef void (*SGSubsystemTimingCb)(void* userData, const std::string& name, Sam * subsystems may also override the suspend() and resume() methods to * take different actions.

*/ + class SGSubsystem : public SGReferenced { public: @@ -281,19 +282,19 @@ public: /** * composite name for this subsystem (type name & optional instance name) */ - std::string name() const - { return _name; } + std::string subsystemId() const + { return _subsystemId; } /** * @brief the type (class)-specific part of the subsystem name. */ - std::string typeName() const; + std::string subsystemClassId() const; /** * @brief the instance part of the subsystem name. Empty if this * subsystem is not instanced */ - std::string instanceName() const; + std::string subsystemInstanceId() const; virtual bool is_group() const { return false; } @@ -342,26 +343,31 @@ public: protected: friend class SGSubsystemMgr; friend class SGSubsystemGroup; - + void set_name(const std::string& n); void set_group(SGSubsystemGroup* group); - + /// composite name for the subsystem (type name and instance name if this /// is an instanced subsystem. (Since this member was originally defined as /// protected, not private, we can't rename it easily) std::string _name; - bool _suspended = false; + bool _suspended = false; - eventTimeVec timingInfo; + eventTimeVec timingInfo; - static SGSubsystemTimingCb reportTimingCb; - static void* reportTimingUserData; - static bool reportTimingStatsRequest; - static int maxTimePerFrame_ms; + static SGSubsystemTimingCb reportTimingCb; + static void* reportTimingUserData; + static bool reportTimingStatsRequest; + static int maxTimePerFrame_ms; private: + /// composite name for the subsystem (type name and instance name if this + /// is an instanced subsystem. (Since this member was originally defined as + /// protected, not private, we can't rename it easily) + std::string _subsystemId; + SGSubsystemGroup* _group = nullptr; protected: TimerStats _timerStats, _lastTimerStats; @@ -378,20 +384,21 @@ typedef SGSharedPtr SGSubsystemRef; class SGSubsystemGroup : public SGSubsystem { public: - SGSubsystemGroup (const char *name); + SGSubsystemGroup (); virtual ~SGSubsystemGroup (); + // Subsystem API. + void bind() override; + InitStatus incrementalInit() override; void init() override; - InitStatus incrementalInit () override; - void postinit () override; - void reinit () override; - void shutdown () override; - void bind () override; - void unbind () override; - void update (double delta_time_sec) override; - void suspend () override; - void resume () override; - bool is_suspended () const override; + void postinit() override; + void reinit() override; + void resume() override; + void shutdown() override; + void suspend() override; + void unbind() override; + void update(double delta_time_sec) override; + bool is_suspended() const override; virtual void set_subsystem (const std::string &name, SGSubsystem * subsystem, @@ -416,21 +423,22 @@ public: */ void set_fixed_update_time(double fixed_dt); - /** - * retrive list of member subsystem names - */ + /** + * retrive list of member subsystem names + */ string_list member_names() const; template T* get_subsystem() { - return dynamic_cast(get_subsystem(T::subsystemName())); + return dynamic_cast(get_subsystem(T::staticSubsystemClassId())); } bool is_group() const override { return true; } SGSubsystemMgr* get_manager() const override; + private: void forEach(std::function f); void reverseForEach(std::function f); @@ -501,20 +509,24 @@ public: MAX_GROUPS }; - SGSubsystemMgr (const char *name); + SGSubsystemMgr (); virtual ~SGSubsystemMgr (); - void init () override; - InitStatus incrementalInit () override; - void postinit () override; - void reinit () override; - void shutdown () override; - void bind () override; - void unbind () override; - void update (double delta_time_sec) override; - void suspend () override; - void resume () override; - bool is_suspended () const override; + // Subsystem API. + void bind() override; + void init() override; + InitStatus incrementalInit() override; + void postinit() override; + void reinit() override; + void resume() override; + void shutdown() override; + void suspend() override; + void unbind() override; + void update(double delta_time_sec) override; + bool is_suspended() const override; + + // Subsystem identification. + static const char* staticSubsystemClassId() { return "subsystem-mgr"; } virtual void add (const char * name, SGSubsystem * subsystem, @@ -531,7 +543,7 @@ public: SGSubsystem* get_subsystem(const std::string &name) const; - SGSubsystem* get_subsystem(const std::string &name, const std::string& instanceName) const; + SGSubsystem* get_subsystem(const std::string &name, const std::string& subsystemInstanceId) const; void reportTiming(); void setReportTimingCb(void* userData, SGSubsystemTimingCb cb) { reportTimingCb = cb; reportTimingUserData = userData; } @@ -552,34 +564,40 @@ public: template T* get_subsystem() const { - return dynamic_cast(get_subsystem(T::subsystemName())); + return dynamic_cast(get_subsystem(T::staticSubsystemClassId())); } // instanced overloads, for both raw char* and std::string // these concatenate the subsystem type name with the instance name template - T* get_subsystem(const char* instanceName) const + T* get_subsystem(const char* subsystemInstanceId) const { - return dynamic_cast(get_subsystem(T::subsystemName(), instanceName)); + return dynamic_cast(get_subsystem(T::staticSubsystemClassId(), subsystemInstanceId)); } template - T* get_subsystem(const std::string& instanceName) const + T* get_subsystem(const std::string& subsystemInstanceId) const { - return dynamic_cast(get_subsystem(T::subsystemName(), instanceName)); + return dynamic_cast(get_subsystem(T::staticSubsystemClassId(), subsystemInstanceId)); } + /** + * @brief Subsystem dependency structure. + */ struct Dependency { enum Type { - HARD, - SOFT, - PROPERTY + HARD, ///< The subsystem cannot run without this subsystem dependency. + SOFT, ///< The subsystem uses this subsystem dependency, but can run without it. + SEQUENCE, ///< Used for ordering subsystem initialisation. + NONSUBSYSTEM_HARD, ///< The subsystem cannot run without this non-subsystem dependency. + NONSUBSYSTEM_SOFT, ///< The subsystem uses this non-subsystem dependency, but can run without it. + PROPERTY ///< The subsystem requires this property to exist to run. }; - + std::string name; Type type; }; - + using DependencyVec = std::vector; using SubsystemFactoryFunctor = std::function; @@ -598,11 +616,12 @@ public: class Registrant { public: - Registrant(GroupType group = GENERAL, double updateInterval = 0.0, - std::initializer_list deps = {}) + Registrant(GroupType group = GENERAL, + std::initializer_list deps = {}, + double updateInterval = 0.0) { SubsystemFactoryFunctor factory = [](){ return new T; }; - SGSubsystemMgr::registerSubsystem(T::subsystemName(), + SGSubsystemMgr::registerSubsystem(T::staticSubsystemClassId(), factory, group, false, updateInterval, deps); @@ -616,11 +635,11 @@ public: { public: InstancedRegistrant(GroupType group = GENERAL, - double updateInterval = 0.0, - std::initializer_list deps = {}) + std::initializer_list deps = {}, + double updateInterval = 0.0) { SubsystemFactoryFunctor factory = [](){ return new T; }; - SGSubsystemMgr::registerSubsystem(T::subsystemName(), + SGSubsystemMgr::registerSubsystem(T::staticSubsystemClassId(), factory, group, true, updateInterval, deps); @@ -636,14 +655,14 @@ public: template SGSharedPtr add(GroupType customGroup = INVALID, double customInterval = 0.0) { - auto ref = create(T::subsystemName()); + auto ref = create(T::staticSubsystemClassId()); const GroupType group = (customGroup == INVALID) ? - defaultGroupFor(T::subsystemName()) : customGroup; + defaultGroupFor(T::staticSubsystemClassId()) : customGroup; const double interval = (customInterval == 0.0) ? - defaultUpdateIntervalFor(T::subsystemName()) : customInterval; - add(ref->name().c_str(), ref.ptr(), group, interval); + defaultUpdateIntervalFor(T::staticSubsystemClassId()) : customInterval; + add(ref->subsystemId().c_str(), ref.ptr(), group, interval); return dynamic_cast(ref.ptr()); } @@ -654,20 +673,20 @@ public: template SGSharedPtr create() { - auto ref = create(T::subsystemName()); + auto ref = create(T::staticSubsystemClassId()); return dynamic_cast(ref.ptr()); } SGSubsystemRef create(const std::string& name); template - SGSharedPtr createInstance(const std::string& instanceName) + SGSharedPtr createInstance(const std::string& subsystemInstanceId) { - auto ref = createInstance(T::subsystemName(), instanceName); + auto ref = createInstance(T::staticSubsystemClassId(), subsystemInstanceId); return dynamic_cast(ref.ptr()); } - SGSubsystemRef createInstance(const std::string& name, const std::string& instanceName); + SGSubsystemRef createInstance(const std::string& name, const std::string& subsystemInstanceId); static GroupType defaultGroupFor(const char* name); static double defaultUpdateIntervalFor(const char* name); diff --git a/simgear/structure/subsystem_test.cxx b/simgear/structure/subsystem_test.cxx index bdd0a6b5..266b7fdd 100644 --- a/simgear/structure/subsystem_test.cxx +++ b/simgear/structure/subsystem_test.cxx @@ -20,7 +20,7 @@ using std::endl; class MySub1 : public SGSubsystem { public: - static const char* subsystemName() { return "mysub"; } + static const char* staticSubsystemClassId() { return "mysub"; } void init() override { @@ -45,7 +45,7 @@ public: class AnotherSub : public SGSubsystem { public: - static const char* subsystemName() { return "anothersub"; } + static const char* staticSubsystemClassId() { return "anothersub"; } void init() override { @@ -70,7 +70,7 @@ public: class FakeRadioSub : public SGSubsystem { public: - static const char* subsystemName() { return "fake-radio"; } + static const char* staticSubsystemClassId() { return "fake-radio"; } void init() override { @@ -89,8 +89,8 @@ public: class InstrumentGroup : public SGSubsystemGroup { public: - static const char* subsystemName() { return "instruments"; } - InstrumentGroup() : SGSubsystemGroup(InstrumentGroup::subsystemName()) {} + static const char* staticSubsystemClassId() { return "instruments"; } + virtual ~InstrumentGroup() { } @@ -119,11 +119,11 @@ class RecorderDelegate : public SGSubsystemMgr::Delegate public: void willChange(SGSubsystem* sub, SGSubsystem::State newState) override { - events.push_back({sub->name(), false, newState}); + events.push_back({sub->subsystemId(), false, newState}); } void didChange(SGSubsystem* sub, SGSubsystem::State newState) override { - events.push_back({sub->name(), true, newState}); + events.push_back({sub->subsystemId(), true, newState}); } struct Event { @@ -164,14 +164,14 @@ SGSubsystemMgr::InstancedRegistrant registrant3(SGSubsystemMgr::PO void testRegistrationAndCreation() { - SGSharedPtr manager = new SGSubsystemMgr("TEST1"); + SGSharedPtr manager = new SGSubsystemMgr(); auto anotherSub = manager->create(); SG_VERIFY(anotherSub); - SG_CHECK_EQUAL(anotherSub->name(), AnotherSub::subsystemName()); - SG_CHECK_EQUAL(anotherSub->name(), std::string("anothersub")); - SG_CHECK_EQUAL(anotherSub->typeName(), std::string("anothersub")); - SG_CHECK_EQUAL(anotherSub->instanceName(), std::string()); + SG_CHECK_EQUAL(anotherSub->subsystemId(), AnotherSub::staticSubsystemClassId()); + SG_CHECK_EQUAL(anotherSub->subsystemId(), std::string("anothersub")); + SG_CHECK_EQUAL(anotherSub->subsystemClassId(), std::string("anothersub")); + SG_CHECK_EQUAL(anotherSub->subsystemInstanceId(), std::string()); auto radio1 = manager->createInstance("nav1"); auto radio2 = manager->createInstance("nav2"); @@ -181,14 +181,14 @@ void testRegistrationAndCreation() void testAddGetRemove() { - SGSharedPtr manager = new SGSubsystemMgr("TEST1"); + SGSharedPtr manager = new SGSubsystemMgr(); auto d = new RecorderDelegate; manager->addDelegate(d); auto anotherSub = manager->add(); SG_VERIFY(anotherSub); - SG_CHECK_EQUAL(anotherSub->name(), AnotherSub::subsystemName()); - SG_CHECK_EQUAL(anotherSub->name(), std::string("anothersub")); + SG_CHECK_EQUAL(anotherSub->subsystemId(), AnotherSub::staticSubsystemClassId()); + SG_CHECK_EQUAL(anotherSub->subsystemId(), std::string("anothersub")); SG_VERIFY(d->hasEvent("anothersub-will-add")); SG_VERIFY(d->hasEvent("anothersub-did-add")); @@ -200,14 +200,14 @@ void testAddGetRemove() // manual create & add auto mySub = manager->create(); - manager->add(MySub1::subsystemName(), mySub.ptr(), SGSubsystemMgr::DISPLAY, 0.1234); + manager->add(MySub1::staticSubsystemClassId(), mySub.ptr(), SGSubsystemMgr::DISPLAY, 0.1234); SG_VERIFY(d->hasEvent("mysub-will-add")); SG_VERIFY(d->hasEvent("mysub-did-add")); SG_CHECK_EQUAL(manager->get_subsystem(), mySub); - bool ok = manager->remove(AnotherSub::subsystemName()); + bool ok = manager->remove(AnotherSub::staticSubsystemClassId()); SG_VERIFY(ok); SG_VERIFY(d->hasEvent("anothersub-will-remove")); SG_VERIFY(d->hasEvent("anothersub-did-remove")); @@ -219,7 +219,7 @@ void testAddGetRemove() // re-add of removed, and let's test overriding auto another2 = manager->add(SGSubsystemMgr::SOUND); - SG_CHECK_EQUAL(another2->name(), AnotherSub::subsystemName()); + SG_CHECK_EQUAL(another2->subsystemId(), AnotherSub::staticSubsystemClassId()); auto soundGroup = manager->get_group(SGSubsystemMgr::SOUND); SG_CHECK_EQUAL(soundGroup->get_subsystem("anothersub"), another2); @@ -228,7 +228,7 @@ void testAddGetRemove() void testSubGrouping() { - SGSharedPtr manager = new SGSubsystemMgr("TEST1"); + SGSharedPtr manager = new SGSubsystemMgr(); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -238,10 +238,10 @@ void testSubGrouping() auto radio1 = manager->createInstance("nav1"); auto radio2 = manager->createInstance("nav2"); - SG_CHECK_EQUAL(radio1->name(), std::string("fake-radio.nav1")); - SG_CHECK_EQUAL(radio2->name(), std::string("fake-radio.nav2")); - SG_CHECK_EQUAL(radio1->typeName(), std::string("fake-radio")); - SG_CHECK_EQUAL(radio2->instanceName(), std::string("nav2")); + SG_CHECK_EQUAL(radio1->subsystemId(), std::string("fake-radio.nav1")); + SG_CHECK_EQUAL(radio2->subsystemId(), std::string("fake-radio.nav2")); + SG_CHECK_EQUAL(radio1->subsystemClassId(), std::string("fake-radio")); + SG_CHECK_EQUAL(radio2->subsystemInstanceId(), std::string("nav2")); instruments->set_subsystem(radio1); instruments->set_subsystem(radio2); @@ -299,7 +299,7 @@ void testSubGrouping() void testIncrementalInit() { - SGSharedPtr manager = new SGSubsystemMgr("TEST"); + SGSharedPtr manager = new SGSubsystemMgr(); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -347,7 +347,7 @@ void testEmptyGroup() // https://sourceforge.net/p/flightgear/codetickets/2043/ // when an empty group is inited, we skipped setting the state - SGSharedPtr manager = new SGSubsystemMgr("TEST"); + SGSharedPtr manager = new SGSubsystemMgr(); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -371,7 +371,7 @@ void testEmptyGroup() void testSuspendResume() { - SGSharedPtr manager = new SGSubsystemMgr("TEST"); + SGSharedPtr manager = new SGSubsystemMgr(); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -441,7 +441,7 @@ void testSuspendResume() void testPropertyRoot() { - SGSharedPtr manager = new SGSubsystemMgr("TEST"); + SGSharedPtr manager = new SGSubsystemMgr(); SGPropertyNode_ptr props(new SGPropertyNode); manager->set_root_node(props); @@ -467,7 +467,7 @@ void testPropertyRoot() void testAddRemoveAfterInit() { - SGSharedPtr manager = new SGSubsystemMgr("TEST"); + SGSharedPtr manager = new SGSubsystemMgr(); auto d = new RecorderDelegate; manager->addDelegate(d); diff --git a/simgear/threads/SGThread.cxx b/simgear/threads/SGThread.cxx index 0ba71f40..c5686e51 100644 --- a/simgear/threads/SGThread.cxx +++ b/simgear/threads/SGThread.cxx @@ -25,6 +25,7 @@ #endif #include +#include #include "SGThread.hxx" @@ -416,3 +417,98 @@ SGWaitCondition::broadcast() { _privateData->broadcast(); } + + +SGExclusiveThread::SGExclusiveThread() : + _started(false), _terminated(false), last_await_time(0), + dataReady(false), complete(true), process_ran(false), process_running(false) + { + } + + SGExclusiveThread::~SGExclusiveThread() + { + + } + + void SGExclusiveThread::release() { + std::unique_lock lck(mutex_); + if (!complete) { + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] not finished - skipping"); + return; + } + if (!complete.exchange(false)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] concurrent failure (2)"); + if (dataReady.exchange(true)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] concurrent failure (1)"); + condVar.notify_one(); + } + void SGExclusiveThread::wait() { + std::unique_lock lck(mutex_); + if (!dataReady) + { + do + { + condVar.wait(lck); + } while (!dataReady); + } + } + void SGExclusiveThread::clearAwaitCompletionTime() { + last_await_time = 0; + } + void SGExclusiveThread::awaitCompletion() { + timestamp.stamp(); + std::unique_lock lck(Cmutex_); + if (!complete) + { + do { + CcondVar.wait(lck); + } while (!complete.load()); + } + + if (process_ran) { + last_await_time = timestamp.elapsedUSec(); + process_ran = 0; + } + } + + void SGExclusiveThread::setCompletion() { + std::unique_lock lck(Cmutex_); + if (!dataReady.exchange(false)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] atomic operation on dataReady failed (5)\n"); + + if (complete.exchange(true)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] atomic operation on complete failed (5)\n"); + CcondVar.notify_one(); + } + void SGExclusiveThread::run() + { + process_running = true; + while (!_terminated) { + wait(); + process_ran = process(); + setCompletion(); + } + process_running = false; + _terminated = false; + _started = false; + } + + void SGExclusiveThread::terminate() { + _terminated = true; + } + bool SGExclusiveThread::stop() + { + return true; + } + void SGExclusiveThread::ensure_running() + { + if (!_started) + { + _started = true; + start(); + } + } + bool SGExclusiveThread::is_running() + { + return process_running; + } diff --git a/simgear/threads/SGThread.hxx b/simgear/threads/SGThread.hxx index d905c624..e5610f4b 100644 --- a/simgear/threads/SGThread.hxx +++ b/simgear/threads/SGThread.hxx @@ -23,7 +23,11 @@ #ifndef SGTHREAD_HXX_INCLUDED #define SGTHREAD_HXX_INCLUDED 1 +#include +#include +#include #include +#include /** * Encapsulate generic threading methods. @@ -184,4 +188,41 @@ private: PrivateData* _privateData; }; +/// +/// an exclusive thread is one that is designed for frame processing; +/// it has the ability to synchronise such that the caller can await +/// the processing to finish. +class SGExclusiveThread : public SGThread{ +private: + std::mutex mutex_; + std::condition_variable condVar; + SGTimeStamp timestamp; + std::mutex Cmutex_; + std::condition_variable CcondVar; + + bool _started; + bool _terminated; + int last_await_time; + + std::atomic dataReady; + std::atomic complete; + std::atomic process_ran; + std::atomic process_running; + +public: + SGExclusiveThread(); + virtual ~SGExclusiveThread(); + void release(); + void wait(); + void clearAwaitCompletionTime(); + virtual void awaitCompletion(); + void setCompletion(); + virtual int process() = 0; + virtual void run(); + void terminate(); + bool stop(); + void ensure_running(); + bool is_running(); +}; + #endif /* SGTHREAD_HXX_INCLUDED */ diff --git a/simgear/timing/timestamp.cxx b/simgear/timing/timestamp.cxx index 067a1b95..fcb56836 100644 --- a/simgear/timing/timestamp.cxx +++ b/simgear/timing/timestamp.cxx @@ -337,3 +337,11 @@ int SGTimeStamp::elapsedMSec() const return static_cast((now - *this).toMSecs()); } + +int SGTimeStamp::elapsedUSec() const +{ + SGTimeStamp now; + now.stamp(); + + return static_cast((now - *this).toUSecs()); +} diff --git a/simgear/timing/timestamp.hxx b/simgear/timing/timestamp.hxx index 266fd2ab..bf1d5d40 100644 --- a/simgear/timing/timestamp.hxx +++ b/simgear/timing/timestamp.hxx @@ -221,9 +221,13 @@ public: { return sleepFor(fromMSec(msec)); } /** - * elapsed time since the stamp was taken, in msec - */ + * elapsed time since the stamp was taken, in msec + */ int elapsedMSec() const; + /** + * elapsed time since the stamp was taken, in usec + */ + int elapsedUSec() const; private: SGTimeStamp(sec_type sec, nsec_type nsec) { setTime(sec, nsec); }