History

Merge branch 'master' of github.com:highfidelity/hifi into merge/triplelexx-21883

# Conflicts: # interface/src/Application.cpp # interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp

Thijs Wenker committed 8 years ago

Commit 05bc32b2 parents 883cd7b 74e5c4b1575 files · +55139 −32393Browse files

Showing the changes to interface/src/Application.cpp (1 of 1575 files). Show all

Unified · Split

interface/src/Application.cpp+897−471
@@ -144,6 +144,7 @@ · 143 unmodified lines
144144 #include <trackers/EyeTracker.h>
145145 #include <avatars-renderer/ScriptAvatar.h>
146146 #include <RenderableEntityItem.h>
147+#include <procedural/ProceduralSkybox.h>
147148
148149 #include "AudioClient.h"
149150 #include "audio/AudioScope.h"
@@ -218,14 +219,16 @@ · 68 unmodified lines
218219 #include "webbrowser/WebBrowserSuggestionsEngine.h"
219220 #include <DesktopPreviewProvider.h>
220221
222+#include "AboutUtil.h"
223+
221224 #if defined(Q_OS_WIN)
222225 #include <VersionHelpers.h>
223226
224227 #ifdef DEBUG_EVENT_QUEUE
225228 // This is a HACK that uses private headers included with the qt source distrubution.
226229 // To use this feature you need to add these directores to your include path:
227−// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1/QtCore
228−// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1
230+// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1/QtCore
231+// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1
229232 #define QT_BOOTSTRAPPED
230233 #include <private/qthread_p.h>
231234 #include <private/qobject_p.h>
@@ -241,6 +244,7 @@ extern "C" { · 9 unmodified lines
241244
242245 #if defined(Q_OS_ANDROID)
243246 #include <android/log.h>
247+#include "AndroidHelper.h"
244248 #endif
245249
246250 enum ApplicationEvent {
@@ -329,7 +333,11 @@ static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; · 82 unmodified lines
329333 static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
330334 #endif
331335
336+#if !defined(Q_OS_ANDROID)
332337 static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
338+#else
339+static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 4;
340+#endif
333341
334342 // For processing on QThreadPool, we target a number of threads after reserving some
335343 // based on how many are being consumed by the application and the display plugin. However,
@@ -373,7 +381,7 @@ Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTRE · 37 unmodified lines
373381 static const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com";
374382 static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds
375383 static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
376−
384+static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
377385 static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
378386
379387 const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
@@ -489,7 +497,7 @@ public: · 109 unmodified lines
489497 // Don't actually crash in debug builds, in case this apparent deadlock is simply from
490498 // the developer actively debugging code
491499 #ifdef NDEBUG
492− deadlockDetectionCrash();
500+ deadlockDetectionCrash();
493501 #endif
494502 }
495503 }
@@ -687,6 +695,34 @@ private: · 191 unmodified lines
687695 }
688696 };
689697
698+/**jsdoc
699+ * <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
700+ * values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
701+ * <code>Controller.Standard</code> items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method).
702+ * Each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
703+ * <table>
704+ * <thead>
705+ * <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
706+ * </thead>
707+ * <tbody>
708+ * <tr><td><code>CameraFirstPerson</code></td><td>number</td><td>number</td><td>The camera is in first-person mode.
709+ * </td></tr>
710+ * <tr><td><code>CameraThirdPerson</code></td><td>number</td><td>number</td><td>The camera is in third-person mode.
711+ * </td></tr>
712+ * <tr><td><code>CameraFSM</code></td><td>number</td><td>number</td><td>The camera is in full screen mirror mode.</td></tr>
713+ * <tr><td><code>CameraIndependent</code></td><td>number</td><td>number</td><td>The camera is in independent mode.</td></tr>
714+ * <tr><td><code>CameraEntity</code></td><td>number</td><td>number</td><td>The camera is in entity mode.</td></tr>
715+ * <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
716+ * <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement controls are enabled.
717+ * </td></tr>
718+ * <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
719+ * <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
720+ * <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
721+ * </tbody>
722+ * </table>
723+ * @typedef {object} Controller.Hardware-Application
724+ */
725+
690726 static const QString STATE_IN_HMD = "InHMD";
691727 static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM";
692728 static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
@@ -703,6 +739,11 @@ extern DisplayPluginList getDisplayPlugins(); · 10 unmodified lines
703739 extern InputPluginList getInputPlugins();
704740 extern void saveInputPluginSettings(const InputPluginList& plugins);
705741
742+// Parameters used for running tests from teh command line
743+const QString TEST_SCRIPT_COMMAND{ "--testScript" };
744+const QString TEST_QUIT_WHEN_FINISHED_OPTION{ "quitWhenFinished" };
745+const QString TEST_RESULTS_LOCATION_COMMAND{ "--testResultsLocation" };
746+
706747 bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
707748 const char** constArgv = const_cast<const char**>(argv);
708749
@@ -737,7 +778,22 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 28 unmodified lines
737778
738779 static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
739780 bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
740− bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
781+
782+ // Ignore any previous crashes if running from command line with a test script.
783+ bool inTestMode { false };
784+ for (int i = 0; i < argc; ++i) {
785+ QString parameter(argv[i]);
786+ if (parameter == TEST_SCRIPT_COMMAND) {
787+ inTestMode = true;
788+ break;
789+ }
790+ }
791+
792+ bool previousSessionCrashed { false };
793+ if (!inTestMode) {
794+ previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
795+ }
796+
741797 // get dir to use for cache
742798 static const auto CACHE_SWITCH = "--cache";
743799 QString cacheDir = getCmdOption(argc, const_cast<const char**>(argv), CACHE_SWITCH);
@@ -745,15 +801,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 1 unmodified line
745801 qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir);
746802 }
747803
748− // FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove
749− // this conditional exclusion
750−#if !defined(Q_OS_OSX)
751804 {
752−#if defined(Q_OS_ANDROID)
753− const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc";
754−#else
755− const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc";
756−#endif
805+ const QString resourcesBinaryFile = PathUtils::getRccPath();
757806 if (!QFile::exists(resourcesBinaryFile)) {
758807 throw std::runtime_error("Unable to find primary resources");
759808 }
@@ -761,7 +810,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 1 unmodified line
761810 throw std::runtime_error("Unable to load primary resources");
762811 }
763812 }
764−#endif
765813
766814 // Tell the plugin manager about our statically linked plugins
767815 auto pluginManager = PluginManager::getInstance();
@@ -772,7 +820,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 4 unmodified lines
772820 steamClient->init();
773821 }
774822
775− DependencyManager::set<tracing::Tracer>();
776823 PROFILE_SET_THREAD_NAME("Main Thread");
777824
778825 #if defined(Q_OS_WIN)
@@ -801,7 +848,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 22 unmodified lines
801848 DependencyManager::set<Cursor::Manager>();
802849 DependencyManager::set<VirtualPad::Manager>();
803850 DependencyManager::set<DesktopPreviewProvider>();
851+#if defined(Q_OS_ANDROID)
852+ DependencyManager::set<AccountManager>(); // use the default user agent getter
853+#else
804854 DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
855+#endif
805856 DependencyManager::set<StatTracker>();
806857 DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
807858 DependencyManager::set<ScriptInitializerMixin, NativeScriptInitializers>();
@@ -853,7 +904,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 45 unmodified lines
853904 DependencyManager::set<DiscoverabilityManager>();
854905 DependencyManager::set<SceneScriptingInterface>();
855906 DependencyManager::set<OffscreenUi>();
856− DependencyManager::set<AutoUpdater>();
857907 DependencyManager::set<Midi>();
858908 DependencyManager::set<PathUtils>();
859909 DependencyManager::set<InterfaceDynamicFactory>();
@@ -927,7 +977,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 67 unmodified lines
927977 _entitySimulation(new PhysicalEntitySimulation()),
928978 _physicsEngine(new PhysicsEngine(Vectors::ZERO)),
929979 _entityClipboard(new EntityTree()),
930− _lastQueriedTime(usecTimestampNow()),
931980 _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION),
932981 _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES),
933982 _hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
@@ -956,21 +1005,35 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 22 unmodified lines
9561005 auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
9571006 setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
9581007 setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
959−
9601008 {
961− const QString TEST_SCRIPT = "--testScript";
962− const QString TRACE_FILE = "--traceFile";
9631009 const QStringList args = arguments();
1010+
9641011 for (int i = 0; i < args.size() - 1; ++i) {
965− if (args.at(i) == TEST_SCRIPT) {
1012+ if (args.at(i) == TEST_SCRIPT_COMMAND && (i + 1) < args.size()) {
9661013 QString testScriptPath = args.at(i + 1);
967− if (QFileInfo(testScriptPath).exists()) {
1014+
1015+ // If the URL scheme is http(s) or ftp, then use as is, else - treat it as a local file
1016+ // This is done so as not break previous command line scripts
1017+ if (testScriptPath.left(URL_SCHEME_HTTP.length()) == URL_SCHEME_HTTP ||
1018+ testScriptPath.left(URL_SCHEME_FTP.length()) == URL_SCHEME_FTP) {
1019+
1020+ setProperty(hifi::properties::TEST, QUrl::fromUserInput(testScriptPath));
1021+ } else if (QFileInfo(testScriptPath).exists()) {
9681022 setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
9691023 }
970− } else if (args.at(i) == TRACE_FILE) {
971− QString traceFilePath = args.at(i + 1);
972− setProperty(hifi::properties::TRACING, traceFilePath);
973− DependencyManager::get<tracing::Tracer>()->startTracing();
1024+
1025+ // quite when finished parameter must directly follow the test script
1026+ if ((i + 2) < args.size() && args.at(i + 2) == TEST_QUIT_WHEN_FINISHED_OPTION) {
1027+ quitWhenFinished = true;
1028+ }
1029+ } else if (args.at(i) == TEST_RESULTS_LOCATION_COMMAND) {
1030+ // Set test snapshot location only if it is a writeable directory
1031+ QString path(args.at(i + 1));
1032+
1033+ QFileInfo fileInfo(path);
1034+ if (fileInfo.isDir() && fileInfo.isWritable()) {
1035+ TestScriptingInterface::getInstance()->setTestResultsLocation(path);
1036+ }
9741037 }
9751038 }
9761039 }
@@ -1016,6 +1079,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 39 unmodified lines
10161079 auto nodeList = DependencyManager::get<NodeList>();
10171080 nodeList->startThread();
10181081
1082+ const char** constArgv = const_cast<const char**>(argv);
1083+ if (cmdOptionExists(argc, constArgv, "--disableWatchdog")) {
1084+ DISABLE_WATCHDOG = true;
1085+ }
10191086 // Set up a watchdog thread to intentionally crash the application on deadlocks
10201087 if (!DISABLE_WATCHDOG) {
10211088 (new DeadlockWatchdogThread())->start();
@@ -1033,10 +1100,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 11 unmodified lines
10331100 if (steamClient) {
10341101 qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID();
10351102 }
1103+ setCrashAnnotation("steam", property(hifi::properties::STEAM).toBool() ? "1" : "0");
1104+
10361105 qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
10371106 qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
10381107 qCDebug(interfaceapp) << "[VERSION] VERSION:" << BuildInfo::VERSION;
1039− qCDebug(interfaceapp) << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH;
1108+ qCDebug(interfaceapp) << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING;
10401109 qCDebug(interfaceapp) << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
10411110 #if USE_STABLE_GLOBAL_SERVICES
10421111 qCDebug(interfaceapp) << "[VERSION] We will use STABLE global services.";
@@ -1100,10 +1169,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 57 unmodified lines
11001169 auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
11011170 auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
11021171 float distance = glm::distance(myAvatarPosition, position);
1103− bool shouldMute = !audioClient->isMuted() && (distance < radius);
11041172
1105− if (shouldMute) {
1106− audioClient->toggleMute();
1173+ if (distance < radius) {
1174+ audioClient->setMuted(true);
11071175 audioScriptingInterface->environmentMuted();
11081176 }
11091177 });
@@ -1119,6 +1187,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 9 unmodified lines
11191187 const DomainHandler& domainHandler = nodeList->getDomainHandler();
11201188
11211189 connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl)));
1190+ connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){
1191+ setCrashAnnotation("domain", domainURL.toString().toStdString());
1192+ });
11221193 connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
11231194 connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle()));
11241195 connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
@@ -1162,8 +1233,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 37 unmodified lines
11621233 nodeList.data(), SLOT(reset()));
11631234
11641235 auto dialogsManager = DependencyManager::get<DialogsManager>();
1236+#if defined(Q_OS_ANDROID)
1237+ connect(accountManager.data(), &AccountManager::authRequired, this, []() {
1238+ AndroidHelper::instance().showLoginDialog();
1239+ });
1240+#else
11651241 connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
1242+#endif
11661243 connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
1244+ connect(accountManager.data(), &AccountManager::usernameChanged, [](QString username){
1245+ setCrashAnnotation("username", username.toStdString());
1246+ });
11671247
11681248 // set the account manager's root URL and trigger a login request if we don't have the access token
11691249 accountManager->setIsAgent(true);
@@ -1181,6 +1261,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 11 unmodified lines
11811261 connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount);
11821262 connect(this, &Application::activeDisplayPluginChanged, this, [](){
11831263 qApp->setProperty(hifi::properties::HMD, qApp->isHMDMode());
1264+ auto displayPlugin = qApp->getActiveDisplayPlugin();
1265+ setCrashAnnotation("display_plugin", displayPlugin->getName().toStdString());
1266+ setCrashAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0");
11841267 });
11851268 connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode);
11861269
@@ -1188,6 +1271,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 1 unmodified line
11881271 connect(myAvatar.get(), &MyAvatar::positionGoneTo,
11891272 DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
11901273
1274+ connect(myAvatar.get(), &MyAvatar::skeletonModelURLChanged, [](){
1275+ QUrl avatarURL = qApp->getMyAvatar()->getSkeletonModelURL();
1276+ setCrashAnnotation("avatar", avatarURL.toString().toStdString());
1277+ });
1278+
1279+
11911280 // Inititalize sample before registering
11921281 _sampleSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/sample.wav"));
11931282
@@ -1205,6 +1294,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 11 unmodified lines
12051294
12061295 connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] {
12071296 getEntities()->reloadEntityScripts();
1297+ loadAvatarScripts(getMyAvatar()->getScriptUrls());
12081298 }, Qt::QueuedConnection);
12091299
12101300 connect(scriptEngines, &ScriptEngines::scriptLoadError,
@@ -1225,7 +1315,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 14 unmodified lines
12251315 connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
12261316 connect(&_entityEditSender, &EntityEditPacketSender::addingEntityWithCertificate, this, &Application::addingEntityWithCertificate);
12271317
1228− const char** constArgv = const_cast<const char**>(argv);
12291318 QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
12301319 bool success;
12311320 int concurrentDownloads = concurrentDownloadsStr.toInt(&success);
@@ -1268,67 +1357,88 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 36 unmodified lines
12681357 // Make sure the window is set to the correct size by processing the pending events
12691358 QCoreApplication::processEvents();
12701359 _glWidget->createContext();
1271− _glWidget->makeCurrent();
12721360
1361+ // Create the main thread context, the GPU backend
12731362 initializeGL();
1363+ qCDebug(interfaceapp, "Initialized GL");
1364+
1365+ // Initialize the display plugin architecture
1366+ initializeDisplayPlugins();
1367+ qCDebug(interfaceapp, "Initialized Display");
1368+
1369+ // Create the rendering engine. This can be slow on some machines due to lots of
1370+ // GPU pipeline creation.
1371+ initializeRenderEngine();
1372+ qCDebug(interfaceapp, "Initialized Render Engine.");
1373+
1374+ // Initialize the user interface and menu system
1375+ // Needs to happen AFTER the render engine initialization to access its configuration
1376+ initializeUi();
1377+
1378+ init();
1379+ qCDebug(interfaceapp, "init() complete.");
1380+
1381+ // create thread for parsing of octree data independent of the main network and rendering threads
1382+ _octreeProcessor.initialize(_enableProcessOctreeThread);
1383+ connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
1384+ _entityEditSender.initialize(_enableProcessOctreeThread);
1385+
1386+ _idleLoopStdev.reset();
1387+
1388+ // update before the first render
1389+ update(0);
1390+
12741391 // Make sure we don't time out during slow operations at startup
12751392 updateHeartbeat();
12761393
1277− // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
1278− // The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
1279− static const QString TESTER = "HIFI_TESTER";
1280− auto gpuIdent = GPUIdent::getInstance();
1281− auto glContextData = getGLContextData();
1282− QJsonObject properties = {
1283− { "version", applicationVersion() },
1284− { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) },
1285− { "previousSessionCrashed", _previousSessionCrashed },
1286− { "previousSessionRuntime", sessionRunTime.get() },
1287− { "cpu_architecture", QSysInfo::currentCpuArchitecture() },
1288− { "kernel_type", QSysInfo::kernelType() },
1289− { "kernel_version", QSysInfo::kernelVersion() },
1290− { "os_type", QSysInfo::productType() },
1291− { "os_version", QSysInfo::productVersion() },
1292− { "gpu_name", gpuIdent->getName() },
1293− { "gpu_driver", gpuIdent->getDriver() },
1294− { "gpu_memory", static_cast<qint64>(gpuIdent->getMemory()) },
1295− { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) },
1296− { "gl_version", glContextData["version"] },
1297− { "gl_vender", glContextData["vendor"] },
1298− { "gl_sl_version", glContextData["sl_version"] },
1299− { "gl_renderer", glContextData["renderer"] },
1300− { "ideal_thread_count", QThread::idealThreadCount() }
1301− };
1302− auto macVersion = QSysInfo::macVersion();
1303− if (macVersion != QSysInfo::MV_None) {
1304− properties["os_osx_version"] = QSysInfo::macVersion();
1305− }
1306− auto windowsVersion = QSysInfo::windowsVersion();
1307− if (windowsVersion != QSysInfo::WV_None) {
1308− properties["os_win_version"] = QSysInfo::windowsVersion();
1394+ constexpr auto INSTALLER_INI_NAME = "installer.ini";
1395+ auto iniPath = QDir(applicationDirPath()).filePath(INSTALLER_INI_NAME);
1396+ QFile installerFile { iniPath };
1397+ std::unordered_map<QString, QString> installerKeyValues;
1398+ if (installerFile.open(QIODevice::ReadOnly)) {
1399+ while (!installerFile.atEnd()) {
1400+ auto line = installerFile.readLine();
1401+ if (!line.isEmpty()) {
1402+ auto index = line.indexOf("=");
1403+ if (index >= 0) {
1404+ installerKeyValues[line.mid(0, index).trimmed()] = line.mid(index + 1).trimmed();
1405+ }
1406+ }
1407+ }
13091408 }
13101409
1311− ProcessorInfo procInfo;
1312− if (getProcessorInfo(procInfo)) {
1313− properties["processor_core_count"] = procInfo.numProcessorCores;
1314− properties["logical_processor_count"] = procInfo.numLogicalProcessors;
1315− properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1;
1316− properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2;
1317− properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3;
1410+ // In practice we shouldn't run across installs that don't have a known installer type.
1411+ // Client or Client+Server installs should always have the installer.ini next to their
1412+ // respective interface.exe, and Steam installs will be detected as such. If a user were
1413+ // to delete the installer.ini, though, and as an example, we won't know the context of the
1414+ // original install.
1415+ constexpr auto INSTALLER_KEY_TYPE = "type";
1416+ constexpr auto INSTALLER_KEY_CAMPAIGN = "campaign";
1417+ constexpr auto INSTALLER_TYPE_UNKNOWN = "unknown";
1418+ constexpr auto INSTALLER_TYPE_STEAM = "steam";
1419+
1420+ auto typeIt = installerKeyValues.find(INSTALLER_KEY_TYPE);
1421+ QString installerType = INSTALLER_TYPE_UNKNOWN;
1422+ if (typeIt == installerKeyValues.end()) {
1423+ if (property(hifi::properties::STEAM).toBool()) {
1424+ installerType = INSTALLER_TYPE_STEAM;
1425+ }
1426+ } else {
1427+ installerType = typeIt->second;
13181428 }
13191429
1430+ auto campaignIt = installerKeyValues.find(INSTALLER_KEY_CAMPAIGN);
1431+ QString installerCampaign = campaignIt != installerKeyValues.end() ? campaignIt->second : "";
1432+
1433+ qDebug() << "Detected installer type:" << installerType;
1434+ qDebug() << "Detected installer campaign:" << installerCampaign;
1435+
13201436 // add firstRun flag from settings to launch event
13211437 Setting::Handle<bool> firstRun { Settings::firstRun, true };
13221438
1323− // once the settings have been loaded, check if we need to flip the default for UserActivityLogger
1324− auto& userActivityLogger = UserActivityLogger::getInstance();
1325− if (!userActivityLogger.isDisabledSettingSet()) {
1326− // the user activity logger is opt-out for Interface
1327− // but it's defaulted to disabled for other targets
1328− // so we need to enable it here if it has never been disabled by the user
1329− userActivityLogger.disable(false);
1330− }
1439+ QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
13311440
1441+ auto& userActivityLogger = UserActivityLogger::getInstance();
13321442 if (userActivityLogger.isEnabled()) {
13331443 // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
13341444 // The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
@@ -1338,6 +1448,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 3 unmodified lines
13381448 QJsonObject properties = {
13391449 { "version", applicationVersion() },
13401450 { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) },
1451+ { "installer_campaign", installerCampaign },
1452+ { "installer_type", installerType },
1453+ { "build_type", BuildInfo::BUILD_TYPE_STRING },
13411454 { "previousSessionCrashed", _previousSessionCrashed },
13421455 { "previousSessionRuntime", sessionRunTime.get() },
13431456 { "cpu_architecture", QSysInfo::currentCpuArchitecture() },
@@ -1376,11 +1489,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 32 unmodified lines
13761489 properties["first_run"] = firstRun.get();
13771490
13781491 // add the user's machine ID to the launch event
1379− properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
1492+ properties["machine_fingerprint"] = machineFingerPrint;
13801493
13811494 userActivityLogger.logAction("launch", properties);
13821495 }
13831496
1497+ setCrashAnnotation("machine_fingerprint", machineFingerPrint.toStdString());
1498+
13841499 _entityEditSender.setMyAvatar(myAvatar.get());
13851500
13861501 // The entity octree will have to know about MyAvatar for the parentJointName import
@@ -1506,7 +1621,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 119 unmodified lines
15061621
15071622 if (state) {
15081623 if (action == controller::toInt(controller::Action::TOGGLE_MUTE)) {
1509− DependencyManager::get<AudioClient>()->toggleMute();
1624+ auto audioClient = DependencyManager::get<AudioClient>();
1625+ audioClient->setMuted(!audioClient->isMuted());
15101626 } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
15111627 cycleCamera();
15121628 } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
@@ -1591,6 +1707,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 78 unmodified lines
15911707
15921708 loadSettings();
15931709
1710+ updateVerboseLogging();
1711+
15941712 // Now that we've loaded the menu and thus switched to the previous display plugin
15951713 // we can unlock the desktop repositioning code, since all the positions will be
15961714 // relative to the desktop size for this plugin
@@ -1655,8 +1773,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 58 unmodified lines
16551773 // If launched from Steam, let it handle updates
16561774 const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
16571775 bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1;
1658− if (!noUpdater) {
1659− auto applicationUpdater = DependencyManager::get<AutoUpdater>();
1776+ bool buildCanUpdate = BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable
1777+ || BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master;
1778+ if (!noUpdater && buildCanUpdate) {
1779+ constexpr auto INSTALLER_TYPE_CLIENT_ONLY = "client_only";
1780+
1781+ auto applicationUpdater = DependencyManager::set<AutoUpdater>();
1782+
1783+ AutoUpdater::InstallerType type = installerType == INSTALLER_TYPE_CLIENT_ONLY
1784+ ? AutoUpdater::InstallerType::CLIENT_ONLY : AutoUpdater::InstallerType::FULL;
1785+
1786+ applicationUpdater->setInstallerType(type);
1787+ applicationUpdater->setInstallerCampaign(installerCampaign);
16601788 connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
16611789 applicationUpdater->checkForUpdate();
16621790 }
@@ -2035,14 +2163,24 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 372 unmodified lines
20352163 return entityServerNode && !isPhysicsEnabled();
20362164 });
20372165
2038− _snapshotSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/snap.wav"));
2039−
2166+ _snapshotSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/snapshot/snap.wav"));
2167+
20402168 QVariant testProperty = property(hifi::properties::TEST);
20412169 qDebug() << testProperty;
20422170 if (testProperty.isValid()) {
20432171 auto scriptEngines = DependencyManager::get<ScriptEngines>();
20442172 const auto testScript = property(hifi::properties::TEST).toUrl();
2045− scriptEngines->loadScript(testScript, false);
2173+
2174+ // Set last parameter to exit interface when the test script finishes, if so requested
2175+ scriptEngines->loadScript(testScript, false, false, false, false, quitWhenFinished);
2176+
2177+ // This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
2178+ if (arguments().contains("--url")) {
2179+ auto reply = SandboxUtils::getStatus();
2180+ connect(reply, &QNetworkReply::finished, this, [=] {
2181+ handleSandboxStatus(reply);
2182+ });
2183+ }
20462184 } else {
20472185 PROFILE_RANGE(render, "GetSandboxStatus");
20482186 auto reply = SandboxUtils::getStatus();
@@ -2114,6 +2252,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 65 unmodified lines
21142252 _pendingRenderEvent = false;
21152253
21162254 qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
2255+
2256+#if defined(Q_OS_ANDROID)
2257+ AndroidHelper::instance().init();
2258+ connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
2259+ connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
2260+ AndroidHelper::instance().notifyLoadComplete();
2261+#endif
2262+}
2263+
2264+void Application::updateVerboseLogging() {
2265+ auto menu = Menu::getInstance();
2266+ if (!menu) {
2267+ return;
2268+ }
2269+ bool enable = menu->isOptionChecked(MenuOption::VerboseLogging);
2270+
2271+ QString rules =
2272+ "hifi.*.debug=%1\n"
2273+ "hifi.*.info=%1\n"
2274+ "hifi.audio-stream.debug=false\n"
2275+ "hifi.audio-stream.info=false";
2276+ rules = rules.arg(enable ? "true" : "false");
2277+ QLoggingCategory::setFilterRules(rules);
21172278 }
21182279
21192280 void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
@@ -2226,6 +2387,10 @@ void Application::onAboutToQuit() { · 106 unmodified lines
22262387 }
22272388 }
22282389
2390+ // The active display plugin needs to be loaded before the menu system is active,
2391+ // so its persisted explicitly here
2392+ Setting::Handle<QString>{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName());
2393+
22292394 getActiveDisplayPlugin()->deactivate();
22302395 if (_autoSwitchDisplayModeSupportedHMDPlugin
22312396 && _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) {
@@ -2315,6 +2480,7 @@ void Application::cleanupBeforeQuit() { · 83 unmodified lines
23152480 }
23162481
23172482 _window->saveGeometry();
2483+ _gpuContext->shutdown();
23182484
23192485 // Destroy third party processes after scripts have finished using them.
23202486 #ifdef HAVE_DDE
@@ -2432,48 +2598,171 @@ void Application::initializeGL() { · 111 unmodified lines
24322598 _isGLInitialized = true;
24332599 }
24342600
2435− // Build a shared canvas / context for the Chromium processes
2436− _glWidget->makeCurrent();
2601+ if (!_glWidget->makeCurrent()) {
2602+ qCWarning(interfaceapp, "Unable to make window context current");
2603+ }
24372604
24382605 #if !defined(DISABLE_QML)
2439− // Chromium rendering uses some GL functions that prevent nSight from capturing
2440− // frames, so we only create the shared context if nsight is NOT active.
2441− if (!nsightActive()) {
2442− _chromiumShareContext = new OffscreenGLCanvas();
2443− _chromiumShareContext->setObjectName("ChromiumShareContext");
2444− _chromiumShareContext->create(_glWidget->qglContext());
2445− _chromiumShareContext->makeCurrent();
2446− if (!_chromiumShareContext->makeCurrent()) {
2447− qCWarning(interfaceapp, "Unable to make chromium shared context current");
2448− }
2449− qt_gl_set_global_share_context(_chromiumShareContext->getContext());
2450− } else {
2451− qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
2606+ // Build a shared canvas / context for the Chromium processes
2607+ {
2608+ // Disable signed distance field font rendering on ATI/AMD GPUs, due to
2609+ // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
2610+ std::string vendor{ (const char*)glGetString(GL_VENDOR) };
2611+ if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
2612+ qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
2613+ }
2614+
2615+ // Chromium rendering uses some GL functions that prevent nSight from capturing
2616+ // frames, so we only create the shared context if nsight is NOT active.
2617+ if (!nsightActive()) {
2618+ _chromiumShareContext = new OffscreenGLCanvas();
2619+ _chromiumShareContext->setObjectName("ChromiumShareContext");
2620+ _chromiumShareContext->create(_glWidget->qglContext());
2621+ if (!_chromiumShareContext->makeCurrent()) {
2622+ qCWarning(interfaceapp, "Unable to make chromium shared context current");
2623+ }
2624+ qt_gl_set_global_share_context(_chromiumShareContext->getContext());
2625+ _chromiumShareContext->doneCurrent();
2626+ // Restore the GL widget context
2627+ if (!_glWidget->makeCurrent()) {
2628+ qCWarning(interfaceapp, "Unable to make window context current");
2629+ }
2630+ } else {
2631+ qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering";
2632+ }
24522633 }
24532634 #endif
24542635
24552636 // Build a shared canvas / context for the QML rendering
2637+ {
2638+ _qmlShareContext = new OffscreenGLCanvas();
2639+ _qmlShareContext->setObjectName("QmlShareContext");
2640+ _qmlShareContext->create(_glWidget->qglContext());
2641+ if (!_qmlShareContext->makeCurrent()) {
2642+ qCWarning(interfaceapp, "Unable to make QML shared context current");
2643+ }
2644+ OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
2645+ _qmlShareContext->doneCurrent();
2646+ if (!_glWidget->makeCurrent()) {
2647+ qCWarning(interfaceapp, "Unable to make window context current");
2648+ }
2649+ }
2650+
2651+ // Build an offscreen GL context for the main thread.
2652+ _offscreenContext = new OffscreenGLCanvas();
2653+ _offscreenContext->setObjectName("MainThreadContext");
2654+ _offscreenContext->create(_glWidget->qglContext());
2655+ if (!_offscreenContext->makeCurrent()) {
2656+ qFatal("Unable to make offscreen context current");
2657+ }
2658+ _offscreenContext->doneCurrent();
2659+ _offscreenContext->setThreadContext();
2660+
24562661 _glWidget->makeCurrent();
2457− _qmlShareContext = new OffscreenGLCanvas();
2458− _qmlShareContext->setObjectName("QmlShareContext");
2459− _qmlShareContext->create(_glWidget->qglContext());
2460− if (!_qmlShareContext->makeCurrent()) {
2461− qCWarning(interfaceapp, "Unable to make QML shared context current");
2662+ glClearColor(0.2f, 0.2f, 0.2f, 1);
2663+ glClear(GL_COLOR_BUFFER_BIT);
2664+ _glWidget->swapBuffers();
2665+
2666+ // Move the GL widget context to the render event handler thread
2667+ _renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
2668+ if (!_offscreenContext->makeCurrent()) {
2669+ qFatal("Unable to make offscreen context current");
24622670 }
2463− OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
2464− _qmlShareContext->doneCurrent();
24652671
2672+ // Create the GPU backend
2673+
2674+ // Requires the window context, because that's what's used in the actual rendering
2675+ // and the GPU backend will make things like the VAO which cannot be shared across
2676+ // contexts
24662677 _glWidget->makeCurrent();
24672678 gpu::Context::init<gpu::gl::GLBackend>();
24682679 qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
24692680 QVariant::fromValue((void*)(&gpu::gl::GLBackend::makeProgram)));
2470− _gpuContext = std::make_shared<gpu::Context>();
2471− // The gpu context can make child contexts for transfers, so
2472− // we need to restore primary rendering context
24732681 _glWidget->makeCurrent();
2682+ _gpuContext = std::make_shared<gpu::Context>();
2683+
2684+ DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
2685+
2686+ // Restore the default main thread context
2687+ _offscreenContext->makeCurrent();
2688+}
2689+
2690+static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
2691+
2692+void Application::initializeDisplayPlugins() {
2693+ auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
2694+ Setting::Handle<QString> activeDisplayPluginSetting{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() };
2695+ auto lastActiveDisplayPluginName = activeDisplayPluginSetting.get();
2696+
2697+ auto defaultDisplayPlugin = displayPlugins.at(0);
2698+ // Once time initialization code
2699+ DisplayPluginPointer targetDisplayPlugin;
2700+ foreach(auto displayPlugin, displayPlugins) {
2701+ displayPlugin->setContext(_gpuContext);
2702+ if (displayPlugin->getName() == lastActiveDisplayPluginName) {
2703+ targetDisplayPlugin = displayPlugin;
2704+ }
2705+ QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged,
2706+ [this](const QSize& size) { resizeGL(); });
2707+ QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
2708+ }
2709+
2710+ // The default display plugin needs to be activated first, otherwise the display plugin thread
2711+ // may be launched by an external plugin, which is bad
2712+ setDisplayPlugin(defaultDisplayPlugin);
2713+
2714+ // Now set the desired plugin if it's not the same as the default plugin
2715+ if (targetDisplayPlugin != defaultDisplayPlugin) {
2716+ setDisplayPlugin(targetDisplayPlugin);
2717+ }
2718+
2719+ // Submit a default frame to render until the engine starts up
2720+ updateRenderArgs(0.0f);
2721+
2722+ _offscreenContext->makeCurrent();
2723+
2724+#define ENABLE_SPLASH_FRAME 0
2725+#if ENABLE_SPLASH_FRAME
2726+ {
2727+ QMutexLocker viewLocker(&_renderArgsMutex);
2728+
2729+ if (_appRenderArgs._isStereo) {
2730+ _gpuContext->enableStereo(true);
2731+ _gpuContext->setStereoProjections(_appRenderArgs._eyeProjections);
2732+ _gpuContext->setStereoViews(_appRenderArgs._eyeOffsets);
2733+ }
2734+
2735+ // Frame resources
2736+ auto framebufferCache = DependencyManager::get<FramebufferCache>();
2737+ gpu::FramebufferPointer finalFramebuffer = framebufferCache->getFramebuffer();
2738+ std::shared_ptr<ProceduralSkybox> procedural = std::make_shared<ProceduralSkybox>();
2739+ procedural->parse(SPLASH_SKYBOX);
2740+
2741+ _gpuContext->beginFrame(_appRenderArgs._view, _appRenderArgs._headPose);
2742+ gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
2743+ batch.resetStages();
2744+ batch.enableStereo(false);
2745+ batch.setFramebuffer(finalFramebuffer);
2746+ batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, { 0, 0, 0, 1 });
2747+ batch.enableSkybox(true);
2748+ batch.enableStereo(_appRenderArgs._isStereo);
2749+ batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
2750+ procedural->render(batch, _appRenderArgs._renderArgs.getViewFrustum());
2751+ });
2752+ auto frame = _gpuContext->endFrame();
2753+ frame->frameIndex = 0;
2754+ frame->framebuffer = finalFramebuffer;
2755+ frame->pose = _appRenderArgs._headPose;
2756+ frame->framebufferRecycler = [framebufferCache, procedural](const gpu::FramebufferPointer& framebuffer) {
2757+ framebufferCache->releaseFramebuffer(framebuffer);
2758+ };
2759+ _displayPlugin->submitFrame(frame);
2760+ }
2761+#endif
2762+}
24742763
2475− initDisplay();
2476− qCDebug(interfaceapp, "Initialized Display.");
2764+void Application::initializeRenderEngine() {
2765+ _offscreenContext->makeCurrent();
24772766
24782767 // FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
24792768 DeadlockWatchdogThread::withPause([&] {
@@ -2490,67 +2779,12 @@ void Application::initializeGL() { · 10 unmodified lines
24902779 // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
24912780 DependencyManager::get<GeometryCache>()->initializeShapePipelines();
24922781 });
2493−
2494− _offscreenContext = new OffscreenGLCanvas();
2495− _offscreenContext->setObjectName("MainThreadContext");
2496− _offscreenContext->create(_glWidget->qglContext());
2497− if (!_offscreenContext->makeCurrent()) {
2498− qFatal("Unable to make offscreen context current");
2499− }
2500− _offscreenContext->doneCurrent();
2501− _offscreenContext->setThreadContext();
2502− _renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
2503−
2504− // The UI can't be created until the primary OpenGL
2505− // context is created, because it needs to share
2506− // texture resources
2507− // Needs to happen AFTER the render engine initialization to access its configuration
2508− if (!_offscreenContext->makeCurrent()) {
2509− qFatal("Unable to make offscreen context current");
2510− }
2511−
2512− initializeUi();
2513− qCDebug(interfaceapp, "Initialized Offscreen UI.");
2514−
2515− if (!_offscreenContext->makeCurrent()) {
2516− qFatal("Unable to make offscreen context current");
2517− }
2518− init();
2519− qCDebug(interfaceapp, "init() complete.");
2520−
2521− // create thread for parsing of octree data independent of the main network and rendering threads
2522− _octreeProcessor.initialize(_enableProcessOctreeThread);
2523− connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
2524− _entityEditSender.initialize(_enableProcessOctreeThread);
2525−
2526− _idleLoopStdev.reset();
2527−
2528−
2529− // Restore the primary GL content for the main thread
2530− if (!_offscreenContext->makeCurrent()) {
2531− qFatal("Unable to make offscreen context current");
2532− }
2533−
2534− // update before the first render
2535− update(0);
25362782 }
25372783
25382784 extern void setupPreferences();
2785+static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false);
25392786
25402787 void Application::initializeUi() {
2541− // Make sure all QML surfaces share the main thread GL context
2542− OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
2543− OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" },
2544− [](QQmlContext* context) {
2545− qDebug() << "Whitelist OverlayWindow worked";
2546− context->setContextProperty("OverlayWindowTestString", "TestWorked");
2547− });
2548− OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" },
2549− [](QQmlContext* context) {
2550− qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked";
2551− });
2552−
2553−
25542788 AddressBarDialog::registerType();
25552789 ErrorDialog::registerType();
25562790 LoginDialog::registerType();
@@ -2565,6 +2799,7 @@ void Application::initializeUi() { · 8 unmodified lines
25652799 QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" },
25662800 QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" },
25672801 QUrl{ "hifi/commerce/common/SortableListModel.qml" },
2802+ QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" },
25682803 QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" },
25692804 QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" },
25702805 QUrl{ "hifi/commerce/purchases/Purchases.qml" },
@@ -2577,7 +2812,6 @@ void Application::initializeUi() { · 6 unmodified lines
25772812 QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" },
25782813 QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" },
25792814 QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" },
2580− QUrl{ "hifi/commerce/wallet/sendMoney/SendMoney.qml" },
25812815 QUrl{ "hifi/commerce/wallet/Wallet.qml" },
25822816 QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
25832817 QUrl{ "hifi/commerce/wallet/WalletSetup.qml" },
@@ -2623,6 +2857,9 @@ void Application::initializeUi() { · 39 unmodified lines
26232857 offscreenUi->resume();
26242858 connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
26252859 resizeGL();
2860+ if (_touchscreenVirtualPadDevice) {
2861+ _touchscreenVirtualPadDevice->resize();
2862+ }
26262863 });
26272864
26282865 // This will set up the input plugins UI
@@ -2636,6 +2873,13 @@ void Application::initializeUi() { · 7 unmodified lines
26362873 }
26372874 if (TouchscreenVirtualPadDevice::NAME == inputPlugin->getName()) {
26382875 _touchscreenVirtualPadDevice = std::dynamic_pointer_cast<TouchscreenVirtualPadDevice>(inputPlugin);
2876+#if defined(Q_OS_ANDROID)
2877+ auto& virtualPadManager = VirtualPad::Manager::instance();
2878+ connect(&virtualPadManager, &VirtualPad::Manager::hapticFeedbackRequested,
2879+ this, [](int duration) {
2880+ AndroidHelper::instance().performHapticFeedback(duration);
2881+ });
2882+#endif
26392883 }
26402884 }
26412885
@@ -2652,6 +2896,33 @@ void Application::initializeUi() { · 10 unmodified lines
26522896 auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
26532897 offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
26542898 offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
2899+
2900+ flushMenuUpdates();
2901+
2902+ // Now that the menu is instantiated, ensure the display plugin menu is properly updated
2903+ {
2904+ auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
2905+ // first sort the plugins into groupings: standard, advanced, developer
2906+ std::stable_sort(displayPlugins.begin(), displayPlugins.end(),
2907+ [](const DisplayPluginPointer& a, const DisplayPluginPointer& b)->bool { return a->getGrouping() < b->getGrouping(); });
2908+
2909+ int dpIndex = 1;
2910+ // concatenate the groupings into a single list in the order: standard, advanced, developer
2911+ for(const auto& displayPlugin : displayPlugins) {
2912+ addDisplayPluginToMenu(displayPlugin, dpIndex, _displayPlugin == displayPlugin);
2913+ dpIndex++;
2914+ }
2915+
2916+ // after all plugins have been added to the menu, add a separator to the menu
2917+ auto parent = getPrimaryMenu()->getMenu(MenuOption::OutputMenu);
2918+ parent->addSeparator();
2919+ }
2920+
2921+ // The display plugins are created before the menu now, so we need to do this here to hide the menu bar
2922+ // now that it exists
2923+ if (_window && _window->isFullScreen()) {
2924+ setFullscreen(nullptr, true);
2925+ }
26552926 }
26562927
26572928
@@ -2731,6 +3002,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { · 73 unmodified lines
27313002 surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
27323003 surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
27333004 surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
3005+ surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance());
27343006
27353007 if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
27363008 surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
@@ -2744,9 +3016,11 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { · 7 unmodified lines
27443016 auto surfaceContext = DependencyManager::get<OffscreenUi>()->getSurfaceContext();
27453017 surfaceContext->setContextProperty("Stats", Stats::getInstance());
27463018
3019+#if !defined(Q_OS_ANDROID)
27473020 auto offscreenUi = DependencyManager::get<OffscreenUi>();
27483021 auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
27493022 offscreenUi->show(qml, "AvatarInputsBar");
3023+#endif
27503024 }
27513025
27523026 void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
@@ -2797,7 +3071,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { · 44 unmodified lines
27973071 _thirdPersonHMDCameraBoomValid = false;
27983072
27993073 _myCamera.setOrientation(myAvatar->getHead()->getOrientation());
2800− if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
3074+ if (isOptionChecked(MenuOption::CenterPlayerInView)) {
28013075 _myCamera.setPosition(myAvatar->getDefaultEyePosition()
28023076 + _myCamera.getOrientation() * boomOffset);
28033077 }
@@ -2974,7 +3248,8 @@ void Application::resizeGL() { · 170 unmodified lines
29743248 // Set the desired FBO texture size. If it hasn't changed, this does nothing.
29753249 // Otherwise, it must rebuild the FBOs
29763250 uvec2 framebufferSize = displayPlugin->getRecommendedRenderSize();
2977− uvec2 renderSize = uvec2(vec2(framebufferSize) * getRenderResolutionScale());
3251+ float renderResolutionScale = getRenderResolutionScale();
3252+ uvec2 renderSize = uvec2(vec2(framebufferSize) * renderResolutionScale);
29783253 if (_renderResolution != renderSize) {
29793254 _renderResolution = renderSize;
29803255 DependencyManager::get<FramebufferCache>()->setFrameBufferSize(fromGlm(renderSize));
@@ -2991,13 +3266,13 @@ void Application::resizeGL() { · 10 unmodified lines
29913266 }
29923267
29933268 DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
3269+ displayPlugin->setRenderResolutionScale(renderResolutionScale);
29943270 }
29953271
29963272 void Application::handleSandboxStatus(QNetworkReply* reply) {
29973273 PROFILE_RANGE(render, __FUNCTION__);
29983274
29993275 bool sandboxIsRunning = SandboxUtils::readStatus(reply->readAll());
3000− qDebug() << "HandleSandboxStatus" << sandboxIsRunning;
30013276
30023277 enum HandControllerType {
30033278 Vive,
@@ -3050,16 +3325,17 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 46 unmodified lines
30503325 // If this is a first run we short-circuit the address passed in
30513326 if (firstRun.get()) {
30523327 #if !defined(Q_OS_ANDROID)
3053− showHelp();
3054−#endif
30553328 DependencyManager::get<AddressManager>()->goToEntry();
30563329 sentTo = SENT_TO_ENTRY;
3330+#endif
30573331 firstRun.set(false);
30583332
30593333 } else {
3334+#if !defined(Q_OS_ANDROID)
30603335 qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
30613336 DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
30623337 sentTo = SENT_TO_PREVIOUS_LOCATION;
3338+#endif
30633339 }
30643340
30653341 UserActivityLogger::getInstance().logAction("startup_sent_to", {
@@ -3148,6 +3424,10 @@ void Application::loadServerlessDomain(QUrl domainURL) { · 82 unmodified lines
31483424 tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0);
31493425 }
31503426
3427+ std::map<QString, QString> namedPaths = tmpTree->getNamedPaths();
3428+ nodeList->getDomainHandler().connectedToServerless(namedPaths);
3429+
3430+
31513431 _fullSceneReceivedCounter++;
31523432 }
31533433
@@ -3369,7 +3649,6 @@ void Application::keyPressEvent(QKeyEvent* event) { · 215 unmodified lines
33693649 _keysPressed.insert(event->key());
33703650
33713651 _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts
3372−
33733652 // if one of our scripts have asked to capture this event, then stop processing it
33743653 if (_controllerScriptingInterface->isKeyCaptured(event)) {
33753654 return;
@@ -3395,9 +3674,21 @@ void Application::keyPressEvent(QKeyEvent* event) { · 19 unmodified lines
33953674 }
33963675 break;
33973676
3398− case Qt::Key_1:
3399− case Qt::Key_2:
3400− case Qt::Key_3:
3677+ case Qt::Key_1: {
3678+ Menu* menu = Menu::getInstance();
3679+ menu->triggerOption(MenuOption::FirstPerson);
3680+ break;
3681+ }
3682+ case Qt::Key_2: {
3683+ Menu* menu = Menu::getInstance();
3684+ menu->triggerOption(MenuOption::FullscreenMirror);
3685+ break;
3686+ }
3687+ case Qt::Key_3: {
3688+ Menu* menu = Menu::getInstance();
3689+ menu->triggerOption(MenuOption::ThirdPerson);
3690+ break;
3691+ }
34013692 case Qt::Key_4:
34023693 case Qt::Key_5:
34033694 case Qt::Key_6:
@@ -3447,19 +3738,28 @@ void Application::keyPressEvent(QKeyEvent* event) { · 43 unmodified lines
34473738 if (isShifted && isMeta) {
34483739 Menu::getInstance()->triggerOption(MenuOption::Log);
34493740 } else if (isMeta) {
3450− Menu::getInstance()->triggerOption(MenuOption::AddressBar);
3741+ auto dialogsManager = DependencyManager::get<DialogsManager>();
3742+ dialogsManager->toggleAddressBar();
34513743 } else if (isShifted) {
34523744 Menu::getInstance()->triggerOption(MenuOption::LodTools);
34533745 }
34543746 break;
34553747
3748+ case Qt::Key_R:
3749+ if (isMeta && !event->isAutoRepeat()) {
3750+ DependencyManager::get<ScriptEngines>()->reloadAllScripts();
3751+ DependencyManager::get<OffscreenUi>()->clearCache();
3752+ }
3753+ break;
3754+
34563755 case Qt::Key_Asterisk:
34573756 Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
34583757 break;
34593758
34603759 case Qt::Key_M:
34613760 if (isMeta) {
3462− DependencyManager::get<AudioClient>()->toggleMute();
3761+ auto audioClient = DependencyManager::get<AudioClient>();
3762+ audioClient->setMuted(!audioClient->isMuted());
34633763 }
34643764 break;
34653765
@@ -3479,13 +3779,16 @@ void Application::keyPressEvent(QKeyEvent* event) { · 13 unmodified lines
34793779 AudioInjectorOptions options;
34803780 options.localOnly = true;
34813781 options.stereo = true;
3482−
3483− if (_snapshotSoundInjector) {
3484− _snapshotSoundInjector->setOptions(options);
3485− _snapshotSoundInjector->restart();
3486− } else {
3487− QByteArray samples = _snapshotSound->getByteArray();
3488− _snapshotSoundInjector = AudioInjector::playSound(samples, options);
3782+ Setting::Handle<bool> notificationSounds{ MenuOption::NotificationSounds, true};
3783+ Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true};
3784+ if (notificationSounds.get() && notificationSoundSnapshot.get()) {
3785+ if (_snapshotSoundInjector) {
3786+ _snapshotSoundInjector->setOptions(options);
3787+ _snapshotSoundInjector->restart();
3788+ } else {
3789+ QByteArray samples = _snapshotSound->getByteArray();
3790+ _snapshotSoundInjector = AudioInjector::playSound(samples, options);
3791+ }
34893792 }
34903793 takeSnapshot(true);
34913794 break;
@@ -3504,7 +3807,7 @@ void Application::keyPressEvent(QKeyEvent* event) { · 12 unmodified lines
35043807 } else {
35053808 showCursor(Cursor::Icon::DEFAULT);
35063809 }
3507− } else {
3810+ } else if (!event->isAutoRepeat()){
35083811 resetSensors(true);
35093812 }
35103813 break;
@@ -3514,68 +3817,6 @@ void Application::keyPressEvent(QKeyEvent* event) { · 3 unmodified lines
35143817 Menu::getInstance()->triggerOption(MenuOption::Chat);
35153818 break;
35163819
3517−#if 0
3518− case Qt::Key_I:
3519− if (isShifted) {
3520− _myCamera.setEyeOffsetOrientation(glm::normalize(
3521− glm::quat(glm::vec3(0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation()));
3522− } else {
3523− _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0.001, 0));
3524− }
3525− updateProjectionMatrix();
3526− break;
3527−
3528− case Qt::Key_K:
3529− if (isShifted) {
3530− _myCamera.setEyeOffsetOrientation(glm::normalize(
3531− glm::quat(glm::vec3(-0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation()));
3532− } else {
3533− _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, -0.001, 0));
3534− }
3535− updateProjectionMatrix();
3536− break;
3537−
3538− case Qt::Key_J:
3539− if (isShifted) {
3540− QMutexLocker viewLocker(&_viewMutex);
3541− _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() - 0.1f);
3542− } else {
3543− _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(-0.001, 0, 0));
3544− }
3545− updateProjectionMatrix();
3546− break;
3547−
3548− case Qt::Key_M:
3549− if (isShifted) {
3550− QMutexLocker viewLocker(&_viewMutex);
3551− _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() + 0.1f);
3552− } else {
3553− _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0.001, 0, 0));
3554− }
3555− updateProjectionMatrix();
3556− break;
3557−
3558− case Qt::Key_U:
3559− if (isShifted) {
3560− _myCamera.setEyeOffsetOrientation(glm::normalize(
3561− glm::quat(glm::vec3(0, 0, -0.002f)) * _myCamera.getEyeOffsetOrientation()));
3562− } else {
3563− _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, -0.001));
3564− }
3565− updateProjectionMatrix();
3566− break;
3567−
3568− case Qt::Key_Y:
3569− if (isShifted) {
3570− _myCamera.setEyeOffsetOrientation(glm::normalize(
3571− glm::quat(glm::vec3(0, 0, 0.002f)) * _myCamera.getEyeOffsetOrientation()));
3572− } else {
3573− _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, 0.001));
3574− }
3575− updateProjectionMatrix();
3576− break;
3577−#endif
3578−
35793820 case Qt::Key_Slash:
35803821 Menu::getInstance()->triggerOption(MenuOption::Stats);
35813822 break;
@@ -3618,6 +3859,12 @@ void Application::keyPressEvent(QKeyEvent* event) { · 36 unmodified lines
36183859 void Application::keyReleaseEvent(QKeyEvent* event) {
36193860 _keysPressed.remove(event->key());
36203861
3862+#if defined(Q_OS_ANDROID)
3863+ if (event->key() == Qt::Key_Back) {
3864+ event->accept();
3865+ AndroidHelper::instance().requestActivity("Home", false);
3866+ }
3867+#endif
36213868 _controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts
36223869
36233870 // if one of our scripts have asked to capture this event, then stop processing it
@@ -3937,7 +4184,7 @@ bool Application::acceptSnapshot(const QString& urlString) { · 313 unmodified lines
39374184 QUrl url(urlString);
39384185 QString snapshotPath = url.toLocalFile();
39394186
3940− SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
4187+ SnapshotMetaData* snapshotData = DependencyManager::get<Snapshot>()->parseSnapshotData(snapshotPath);
39414188 if (snapshotData) {
39424189 if (!snapshotData->getURL().toString().isEmpty()) {
39434190 DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
@@ -3952,7 +4199,7 @@ bool Application::acceptSnapshot(const QString& urlString) { · 8 unmodified lines
39524199 static uint32_t _renderedFrameIndex { INVALID_FRAME };
39534200
39544201 bool Application::shouldPaint() const {
3955− if (_aboutToQuit) {
4202+ if (_aboutToQuit || _window->isMinimized()) {
39564203 return false;
39574204 }
39584205
@@ -4348,12 +4595,6 @@ void Application::idle() { · 389 unmodified lines
43484595
43494596 _overlayConductor.update(secondsSinceLastUpdate);
43504597
4351− auto myAvatar = getMyAvatar();
4352− if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
4353− Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
4354− Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
4355− cameraMenuChanged();
4356− }
43574598 _gameLoopCounter.increment();
43584599 }
43594600
@@ -4518,12 +4759,15 @@ void Application::loadSettings() { · 158 unmodified lines
45184759 // DONT CHECK IN
45194760 //DependencyManager::get<LODManager>()->setAutomaticLODAdjust(false);
45204761
4521− Menu::getInstance()->loadSettings();
4762+ auto menu = Menu::getInstance();
4763+ menu->loadSettings();
4764+
4765+ // override the menu option show overlays to always be true on startup
4766+ menu->setIsOptionChecked(MenuOption::Overlays, true);
45224767
45234768 // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it.
45244769 auto pluginManager = PluginManager::getInstance();
45254770 auto plugins = pluginManager->getPreferredDisplayPlugins();
4526− auto menu = Menu::getInstance();
45274771 if (plugins.size() > 0) {
45284772 for (auto plugin : plugins) {
45294773 if (auto action = menu->getActionForOption(plugin->getName())) {
@@ -4609,11 +4853,8 @@ QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) { · 79 unmodified lines
46094853 return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z);
46104854 }
46114855
4612−void Application::initDisplay() {
4613−}
4614−
46154856 void Application::init() {
4616−
4857+ _offscreenContext->makeCurrent();
46174858 // Make sure Login state is up to date
46184859 DependencyManager::get<DialogsManager>()->toggleLoginDialog();
46194860 if (!DISABLE_DEFERRED) {
@@ -4688,6 +4929,31 @@ void Application::init() { · 68 unmodified lines
46884929 }, Qt::QueuedConnection);
46894930 }
46904931
4932+void Application::loadAvatarScripts(const QVector<QString>& urls) {
4933+ auto scriptEngines = DependencyManager::get<ScriptEngines>();
4934+ auto runningScripts = scriptEngines->getRunningScripts();
4935+ for (auto url : urls) {
4936+ int index = runningScripts.indexOf(url);
4937+ if (index < 0) {
4938+ auto scriptEnginePointer = scriptEngines->loadScript(url, false);
4939+ if (scriptEnginePointer) {
4940+ scriptEnginePointer->setType(ScriptEngine::Type::AVATAR);
4941+ }
4942+ }
4943+ }
4944+}
4945+
4946+void Application::unloadAvatarScripts() {
4947+ auto scriptEngines = DependencyManager::get<ScriptEngines>();
4948+ auto urls = scriptEngines->getRunningScripts();
4949+ for (auto url : urls) {
4950+ auto scriptEngine = scriptEngines->getScriptEngine(url);
4951+ if (scriptEngine->getType() == ScriptEngine::Type::AVATAR) {
4952+ scriptEngines->stopScript(url, false);
4953+ }
4954+ }
4955+}
4956+
46914957 void Application::updateLOD(float deltaTime) const {
46924958 PerformanceTimer perfTimer("LOD");
46934959 // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
@@ -4703,7 +4969,7 @@ void Application::updateLOD(float deltaTime) const { · 9 unmodified lines
47034969 }
47044970 }
47054971
4706−void Application::pushPostUpdateLambda(void* key, std::function<void()> func) {
4972+void Application::pushPostUpdateLambda(void* key, const std::function<void()>& func) {
47074973 std::unique_lock<std::mutex> guard(_postUpdateLambdasLock);
47084974 _postUpdateLambdas[key] = func;
47094975 }
@@ -4819,12 +5085,12 @@ void Application::updateThreads(float deltaTime) { · 109 unmodified lines
48195085
48205086 void Application::toggleOverlays() {
48215087 auto menu = Menu::getInstance();
4822− menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays));
5088+ menu->setIsOptionChecked(MenuOption::Overlays, !menu->isOptionChecked(MenuOption::Overlays));
48235089 }
48245090
48255091 void Application::setOverlaysVisible(bool visible) {
48265092 auto menu = Menu::getInstance();
4827− menu->setIsOptionChecked(MenuOption::Overlays, true);
5093+ menu->setIsOptionChecked(MenuOption::Overlays, visible);
48285094 }
48295095
48305096 void Application::centerUI() {
@@ -4878,6 +5144,21 @@ void Application::cameraModeChanged() { · 47 unmodified lines
48785144 cameraMenuChanged();
48795145 }
48805146
5147+void Application::changeViewAsNeeded(float boomLength) {
5148+ // Switch between first and third person views as needed
5149+ // This is called when the boom length has changed
5150+ bool boomLengthGreaterThanMinimum = (boomLength > MyAvatar::ZOOM_MIN);
5151+
5152+ if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON && boomLengthGreaterThanMinimum) {
5153+ Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
5154+ Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
5155+ cameraMenuChanged();
5156+ } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON && !boomLengthGreaterThanMinimum) {
5157+ Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
5158+ Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, false);
5159+ cameraMenuChanged();
5160+ }
5161+}
48815162
48825163 void Application::cameraMenuChanged() {
48835164 auto menu = Menu::getInstance();
@@ -4926,7 +5207,7 @@ void Application::reloadResourceCaches() { · 42 unmodified lines
49265207 resetPhysicsReadyInformation();
49275208
49285209 // Query the octree to refresh everything in view
4929− _lastQueriedTime = 0;
5210+ _queryExpiry = SteadyClock::now();
49305211 _octreeQuery.incrementConnectionID();
49315212
49325213 queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
@@ -5060,6 +5341,78 @@ void Application::updateDialogs(float deltaTime) const { · 127 unmodified lines
50605341 }
50615342 }
50625343
5344+void Application::updateSecondaryCameraViewFrustum() {
5345+ // TODO: Fix this by modeling the way the secondary camera works on how the main camera works
5346+ // ie. Use a camera object stored in the game logic and informs the Engine on where the secondary
5347+ // camera should be.
5348+
5349+ // Code based on SecondaryCameraJob
5350+ auto renderConfig = _renderEngine->getConfiguration();
5351+ assert(renderConfig);
5352+ auto camera = dynamic_cast<SecondaryCameraJobConfig*>(renderConfig->getConfig("SecondaryCamera"));
5353+
5354+ if (!camera || !camera->isEnabled()) {
5355+ return;
5356+ }
5357+
5358+ ViewFrustum secondaryViewFrustum;
5359+ if (camera->mirrorProjection && !camera->attachedEntityId.isNull()) {
5360+ auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
5361+ auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);
5362+ glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();
5363+ glm::quat mirrorPropertiesRotation = entityProperties.getRotation();
5364+ glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();
5365+ glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
5366+
5367+ // setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image
5368+ // TODO: we are assuming here that UP is world y-axis
5369+ glm::mat4 worldFromMirrorRotation = glm::mat4_cast(mirrorPropertiesRotation) * glm::scale(vec3(-1.0f, 1.0f, -1.0f));
5370+ glm::mat4 worldFromMirrorTranslation = glm::translate(mirrorPropertiesPosition);
5371+ glm::mat4 worldFromMirror = worldFromMirrorTranslation * worldFromMirrorRotation;
5372+ glm::mat4 mirrorFromWorld = glm::inverse(worldFromMirror);
5373+
5374+ // get mirror camera position by reflecting main camera position's z coordinate in mirror space
5375+ glm::vec3 mainCameraPositionWorld = getCamera().getPosition();
5376+ glm::vec3 mainCameraPositionMirror = vec3(mirrorFromWorld * vec4(mainCameraPositionWorld, 1.0f));
5377+ glm::vec3 mirrorCameraPositionMirror = vec3(mainCameraPositionMirror.x, mainCameraPositionMirror.y,
5378+ -mainCameraPositionMirror.z);
5379+ glm::vec3 mirrorCameraPositionWorld = vec3(worldFromMirror * vec4(mirrorCameraPositionMirror, 1.0f));
5380+
5381+ // set frustum position to be mirrored camera and set orientation to mirror's adjusted rotation
5382+ glm::quat mirrorCameraOrientation = glm::quat_cast(worldFromMirrorRotation);
5383+ secondaryViewFrustum.setPosition(mirrorCameraPositionWorld);
5384+ secondaryViewFrustum.setOrientation(mirrorCameraOrientation);
5385+
5386+ // build frustum using mirror space translation of mirrored camera
5387+ float nearClip = mirrorCameraPositionMirror.z + mirrorPropertiesDimensions.z * 2.0f;
5388+ glm::vec3 upperRight = halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
5389+ glm::vec3 bottomLeft = -halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
5390+ glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, camera->farClipPlaneDistance);
5391+ secondaryViewFrustum.setProjection(frustum);
5392+ } else {
5393+ if (!camera->attachedEntityId.isNull()) {
5394+ auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
5395+ auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);
5396+ secondaryViewFrustum.setPosition(entityProperties.getPosition());
5397+ secondaryViewFrustum.setOrientation(entityProperties.getRotation());
5398+ } else {
5399+ secondaryViewFrustum.setPosition(camera->position);
5400+ secondaryViewFrustum.setOrientation(camera->orientation);
5401+ }
5402+
5403+ float aspectRatio = (float)camera->textureWidth / (float)camera->textureHeight;
5404+ secondaryViewFrustum.setProjection(camera->vFoV,
5405+ aspectRatio,
5406+ camera->nearClipPlaneDistance,
5407+ camera->farClipPlaneDistance);
5408+ }
5409+ // Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,
5410+ // which is not what we want here.
5411+ secondaryViewFrustum.calculate();
5412+
5413+ _conicalViews.push_back(secondaryViewFrustum);
5414+}
5415+
50635416 static bool domainLoadingInProgress = false;
50645417
50655418 void Application::update(float deltaTime) {
@@ -5084,7 +5437,7 @@ void Application::update(float deltaTime) { · 18 unmodified lines
50845437 // process octree stats packets are sent in between full sends of a scene (this isn't currently true).
50855438 // We keep physics disabled until we've received a full scene and everything near the avatar in that
50865439 // scene is ready to compute its collision shape.
5087− if (nearbyEntitiesAreReadyForPhysics()) {
5440+ if (nearbyEntitiesAreReadyForPhysics() && getMyAvatar()->isReadyForPhysics()) {
50885441 _physicsEnabled = true;
50895442 getMyAvatar()->updateMotionBehaviorFromMenu();
50905443 }
@@ -5117,7 +5470,7 @@ void Application::update(float deltaTime) { · 26 unmodified lines
51175470 if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) {
51185471 if (_lastFaceTrackerUpdate > 0
51195472 && ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) {
5120− audioClient->toggleMute();
5473+ audioClient->setMuted(true);
51215474 _lastFaceTrackerUpdate = 0;
51225475 }
51235476 } else {
@@ -5258,12 +5611,12 @@ void Application::update(float deltaTime) { · 134 unmodified lines
52585611 QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
52595612
52605613 {
5261− PROFILE_RANGE(simulation_physics, "Physics");
5262− PerformanceTimer perfTimer("physics");
5614+ PROFILE_RANGE(simulation_physics, "Simulation");
5615+ PerformanceTimer perfTimer("simulation");
52635616 if (_physicsEnabled) {
52645617 {
5265− PROFILE_RANGE(simulation_physics, "PreStep");
5266− PerformanceTimer perfTimer("preStep)");
5618+ PROFILE_RANGE(simulation_physics, "PrePhysics");
5619+ PerformanceTimer perfTimer("prePhysics)");
52675620 {
52685621 const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
52695622 _physicsEngine->removeObjects(motionStates);
@@ -5297,59 +5650,63 @@ void Application::update(float deltaTime) { · 27 unmodified lines
52975650 });
52985651 }
52995652 {
5300− PROFILE_RANGE(simulation_physics, "Step");
5301− PerformanceTimer perfTimer("step");
5653+ PROFILE_RANGE(simulation_physics, "StepPhysics");
5654+ PerformanceTimer perfTimer("stepPhysics");
53025655 getEntities()->getTree()->withWriteLock([&] {
53035656 _physicsEngine->stepSimulation();
53045657 });
53055658 }
53065659 {
5307− PROFILE_RANGE(simulation_physics, "PostStep");
5308− PerformanceTimer perfTimer("postStep");
53095660 if (_physicsEngine->hasOutgoingChanges()) {
5310− // grab the collision events BEFORE handleOutgoingChanges() because at this point
5311− // we have a better idea of which objects we own or should own.
5312− auto& collisionEvents = _physicsEngine->getCollisionEvents();
5313−
5314− getEntities()->getTree()->withWriteLock([&] {
5315− PROFILE_RANGE(simulation_physics, "HandleChanges");
5316− PerformanceTimer perfTimer("handleChanges");
5317−
5318− const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
5319− _entitySimulation->handleChangedMotionStates(outgoingChanges);
5320− avatarManager->handleChangedMotionStates(outgoingChanges);
5321−
5322− const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
5323− _entitySimulation->handleDeactivatedMotionStates(deactivations);
5324− });
5661+ {
5662+ PROFILE_RANGE(simulation_physics, "PostPhysics");
5663+ PerformanceTimer perfTimer("postPhysics");
5664+ // grab the collision events BEFORE handleChangedMotionStates() because at this point
5665+ // we have a better idea of which objects we own or should own.
5666+ auto& collisionEvents = _physicsEngine->getCollisionEvents();
5667+
5668+ getEntities()->getTree()->withWriteLock([&] {
5669+ PROFILE_RANGE(simulation_physics, "HandleChanges");
5670+ PerformanceTimer perfTimer("handleChanges");
5671+
5672+ const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
5673+ _entitySimulation->handleChangedMotionStates(outgoingChanges);
5674+ avatarManager->handleChangedMotionStates(outgoingChanges);
5675+
5676+ const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
5677+ _entitySimulation->handleDeactivatedMotionStates(deactivations);
5678+ });
5679+
5680+ if (!_aboutToQuit) {
5681+ // handleCollisionEvents() AFTER handleChangedMotionStates()
5682+ {
5683+ PROFILE_RANGE(simulation_physics, "CollisionEvents");
5684+ avatarManager->handleCollisionEvents(collisionEvents);
5685+ // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
5686+ // deadlock.)
5687+ _entitySimulation->handleCollisionEvents(collisionEvents);
5688+ }
5689+ }
53255690
5326− if (!_aboutToQuit) {
5327− // handleCollisionEvents() AFTER handleOutgoingChanges()
53285691 {
5329− PROFILE_RANGE(simulation_physics, "CollisionEvents");
5330− avatarManager->handleCollisionEvents(collisionEvents);
5331− // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
5332− // deadlock.)
5333− _entitySimulation->handleCollisionEvents(collisionEvents);
5692+ PROFILE_RANGE(simulation_physics, "MyAvatar");
5693+ myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
53345694 }
53355695
5336− // NOTE: the getEntities()->update() call below will wait for lock
5337− // and will simulate entity motion (the EntityTree has been given an EntitySimulation).
5338− getEntities()->update(true); // update the models...
5339− }
5340−
5341− {
5342− PROFILE_RANGE(simulation_physics, "MyAvatar");
5343− myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
5696+ if (PerformanceTimer::isActive() &&
5697+ Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
5698+ Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsTiming)) {
5699+ _physicsEngine->harvestPerformanceStats();
5700+ }
5701+ // NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework
5702+ _physicsEngine->dumpStatsIfNecessary();
53445703 }
53455704
5346− if (PerformanceTimer::isActive() &&
5347− Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
5348− Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) {
5349− _physicsEngine->harvestPerformanceStats();
5705+ if (!_aboutToQuit) {
5706+ // NOTE: the getEntities()->update() call below will wait for lock
5707+ // and will provide non-physical entity motion
5708+ getEntities()->update(true); // update the models...
53505709 }
5351− // NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework
5352− _physicsEngine->dumpStatsIfNecessary();
53535710 }
53545711 }
53555712 } else {
@@ -5409,6 +5766,13 @@ void Application::update(float deltaTime) { · 53 unmodified lines
54095766 {
54105767 QMutexLocker viewLocker(&_viewMutex);
54115768 _myCamera.loadViewFrustum(_viewFrustum);
5769+
5770+ _conicalViews.clear();
5771+ _conicalViews.push_back(_viewFrustum);
5772+ // TODO: Fix this by modeling the way the secondary camera works on how the main camera works
5773+ // ie. Use a camera object stored in the game logic and informs the Engine on where the secondary
5774+ // camera should be.
5775+ updateSecondaryCameraViewFrustum();
54125776 }
54135777
54145778 quint64 now = usecTimestampNow();
@@ -5418,18 +5782,31 @@ void Application::update(float deltaTime) { · 3 unmodified lines
54185782 PROFILE_RANGE_EX(app, "QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
54195783 PerformanceTimer perfTimer("queryOctree");
54205784 QMutexLocker viewLocker(&_viewMutex);
5421− quint64 sinceLastQuery = now - _lastQueriedTime;
5422− const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
5423− bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
5424− bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum);
5785+
5786+ bool viewIsDifferentEnough = false;
5787+ if (_conicalViews.size() == _lastQueriedViews.size()) {
5788+ for (size_t i = 0; i < _conicalViews.size(); ++i) {
5789+ if (!_conicalViews[i].isVerySimilar(_lastQueriedViews[i])) {
5790+ viewIsDifferentEnough = true;
5791+ break;
5792+ }
5793+ }
5794+ } else {
5795+ viewIsDifferentEnough = true;
5796+ }
5797+
5798+
54255799 // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
5426− if (queryIsDue || viewIsDifferentEnough) {
5427− _lastQueriedTime = now;
5800+ static const std::chrono::seconds MIN_PERIOD_BETWEEN_QUERIES { 3 };
5801+ auto now = SteadyClock::now();
5802+ if (now > _queryExpiry || viewIsDifferentEnough) {
54285803 if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
54295804 queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
54305805 }
5431− sendAvatarViewFrustum();
5432− _lastQueriedViewFrustum = _viewFrustum;
5806+ queryAvatars();
5807+
5808+ _lastQueriedViews = _conicalViews;
5809+ _queryExpiry = now + MIN_PERIOD_BETWEEN_QUERIES;
54335810 }
54345811 }
54355812
@@ -5469,6 +5846,28 @@ void Application::update(float deltaTime) { · 33 unmodified lines
54695846 }
54705847
54715848
5849+ updateRenderArgs(deltaTime);
5850+
5851+ // HACK
5852+ // load the view frustum
5853+ // FIXME: This preDisplayRender call is temporary until we create a separate render::scene for the mirror rendering.
5854+ // Then we can move this logic into the Avatar::simulate call.
5855+ myAvatar->preDisplaySide(&_appRenderArgs._renderArgs);
5856+
5857+
5858+ {
5859+ PerformanceTimer perfTimer("AnimDebugDraw");
5860+ AnimDebugDraw::getInstance().update();
5861+ }
5862+
5863+
5864+ { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over
5865+ PerformanceTimer perfTimer("enqueueFrame");
5866+ getMain3DScene()->enqueueFrame();
5867+ }
5868+}
5869+
5870+void Application::updateRenderArgs(float deltaTime) {
54725871 editRenderArgs([this, deltaTime](AppRenderArgs& appRenderArgs) {
54735872 PerformanceTimer perfTimer("editRenderArgs");
54745873 appRenderArgs._headPose = getHMDSensorPose();
@@ -5492,9 +5891,9 @@ void Application::update(float deltaTime) { · 17 unmodified lines
54925891 QMutexLocker viewLocker(&_viewMutex);
54935892 // adjust near clip plane to account for sensor scaling.
54945893 auto adjustedProjection = glm::perspective(glm::radians(_fieldOfView.get()),
5495− getActiveDisplayPlugin()->getRecommendedAspectRatio(),
5496− DEFAULT_NEAR_CLIP * sensorToWorldScale,
5497− DEFAULT_FAR_CLIP);
5894+ getActiveDisplayPlugin()->getRecommendedAspectRatio(),
5895+ DEFAULT_NEAR_CLIP * sensorToWorldScale,
5896+ DEFAULT_FAR_CLIP);
54985897 _viewFrustum.setProjection(adjustedProjection);
54995898 _viewFrustum.calculate();
55005899 }
@@ -5510,8 +5909,14 @@ void Application::update(float deltaTime) { · 9 unmodified lines
55105909 }
55115910 {
55125911 PROFILE_RANGE(render, "/resizeGL");
5513− PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings));
5514− bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
5912+ bool showWarnings = false;
5913+ bool suppressShortTimings = false;
5914+ auto menu = Menu::getInstance();
5915+ if (menu) {
5916+ suppressShortTimings = menu->isOptionChecked(MenuOption::SuppressShortTimings);
5917+ showWarnings = menu->isOptionChecked(MenuOption::PipelineWarnings);
5918+ }
5919+ PerformanceWarning::setSuppressShortTimings(suppressShortTimings);
55155920 PerformanceWarning warn(showWarnings, "Application::paintGL()");
55165921 resizeGL();
55175922 }
@@ -5567,12 +5972,6 @@ void Application::update(float deltaTime) { · 49 unmodified lines
55675972 }
55685973 }
55695974
5570− // HACK
5571− // load the view frustum
5572− // FIXME: This preDisplayRender call is temporary until we create a separate render::scene for the mirror rendering.
5573− // Then we can move this logic into the Avatar::simulate call.
5574− myAvatar->preDisplaySide(&appRenderArgs._renderArgs);
5575−
55765975 {
55775976 QMutexLocker viewLocker(&_viewMutex);
55785977 _myCamera.loadViewFrustum(_displayViewFrustum);
@@ -5584,22 +5983,22 @@ void Application::update(float deltaTime) { · 5 unmodified lines
55845983 appRenderArgs._renderArgs.setViewFrustum(_displayViewFrustum);
55855984 }
55865985 });
5986+}
55875987
5588− {
5589− PerformanceTimer perfTimer("AnimDebugDraw");
5590− AnimDebugDraw::getInstance().update();
5591− }
5988+void Application::queryAvatars() {
5989+ auto avatarPacket = NLPacket::create(PacketType::AvatarQuery);
5990+ auto destinationBuffer = reinterpret_cast<unsigned char*>(avatarPacket->getPayload());
5991+ unsigned char* bufferStart = destinationBuffer;
55925992
5593− { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over
5594− PerformanceTimer perfTimer("enqueueFrame");
5595− getMain3DScene()->enqueueFrame();
5993+ uint8_t numFrustums = (uint8_t)_conicalViews.size();
5994+ memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums));
5995+ destinationBuffer += sizeof(numFrustums);
5996+
5997+ for (const auto& view : _conicalViews) {
5998+ destinationBuffer += view.serialize(destinationBuffer);
55965999 }
5597−}
55986000
5599−void Application::sendAvatarViewFrustum() {
5600− QByteArray viewFrustumByteArray = _viewFrustum.toByteArray();
5601− auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size());
5602− avatarPacket->write(viewFrustumByteArray);
6001+ avatarPacket->setPayloadSize(destinationBuffer - bufferStart);
56036002
56046003 DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
56056004 }
@@ -5661,16 +6060,8 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) { · 55 unmodified lines
56616060 return; // bail early if settings are not loaded
56626061 }
56636062
5664− ViewFrustum viewFrustum;
5665− copyViewFrustum(viewFrustum);
5666− _octreeQuery.setCameraPosition(viewFrustum.getPosition());
5667− _octreeQuery.setCameraOrientation(viewFrustum.getOrientation());
5668− _octreeQuery.setCameraFov(viewFrustum.getFieldOfView());
5669− _octreeQuery.setCameraAspectRatio(viewFrustum.getAspectRatio());
5670− _octreeQuery.setCameraNearClip(viewFrustum.getNearClip());
5671− _octreeQuery.setCameraFarClip(viewFrustum.getFarClip());
5672− _octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
5673− _octreeQuery.setCameraCenterRadius(viewFrustum.getCenterRadius());
6063+ _octreeQuery.setConicalViews(_conicalViews);
6064+
56746065 auto lodManager = DependencyManager::get<LODManager>();
56756066 _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
56766067 _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
@@ -5768,7 +6159,9 @@ void Application::updateWindowTitle() const { · 91 unmodified lines
57686159 auto nodeList = DependencyManager::get<NodeList>();
57696160 auto accountManager = DependencyManager::get<AccountManager>();
57706161
5771− QString buildVersion = " (build " + applicationVersion() + ")";
6162+ QString buildVersion = " - "
6163+ + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
6164+ + " " + applicationVersion();
57726165
57736166 QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)";
57746167
@@ -5879,7 +6272,7 @@ void Application::nodeActivated(SharedNodePointer node) { · 104 unmodified lines
58796272 // If we get a new EntityServer activated, reset lastQueried time
58806273 // so we will do a proper query during update
58816274 if (node->getType() == NodeType::EntityServer) {
5882− _lastQueriedTime = 0;
6275+ _queryExpiry = SteadyClock::now();
58836276 _octreeQuery.incrementConnectionID();
58846277 }
58856278
@@ -5888,6 +6281,8 @@ void Application::nodeActivated(SharedNodePointer node) { · 2 unmodified lines
58886281 }
58896282
58906283 if (node->getType() == NodeType::AvatarMixer) {
6284+ _queryExpiry = SteadyClock::now();
6285+
58916286 // new avatar mixer, send off our identity packet on next update loop
58926287 // Reset skeletonModelUrl if the last server modified our choice.
58936288 // Override the avatar url (but not model name) here too.
@@ -5900,6 +6295,9 @@ void Application::nodeActivated(SharedNodePointer node) { · 6 unmodified lines
59006295 }
59016296 getMyAvatar()->markIdentityDataChanged();
59026297 getMyAvatar()->resetLastSent();
6298+
6299+ // transmit a "sendAll" packet to the AvatarMixer we just connected to.
6300+ getMyAvatar()->sendAvatarDataPacket(true);
59036301 }
59046302 }
59056303
@@ -6005,9 +6403,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { · 99 unmodified lines
60056403 bool result = true;
60066404 foreach (EntityItemPointer entity, entities) {
60076405 if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) {
6008− static QString repeatedMessage =
6009− LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*");
6010− qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName();
6406+ HIFI_FCDEBUG(interfaceapp(), "Physics disabled until entity loads: " << entity->getID() << entity->getName());
60116407 // don't break here because we want all the relevant entities to start their downloads
60126408 result = false;
60136409 }
@@ -6191,8 +6587,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe · 177 unmodified lines
61916587 scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
61926588 scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
61936589 scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data());
6194−
6195− scriptEngine->registerGlobalObject("App", this);
6590+ scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance());
61966591
61976592 qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
61986593
@@ -6510,12 +6905,7 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const · 311 unmodified lines
65106905 if (onTablet) {
65116906 toggleTabletUI(true);
65126907 }
6513− }
6514−
6515− if (!onTablet) {
6516− DependencyManager::get<OffscreenUi>()->show(widgetUrl, name);
6517− }
6518− if (tablet->getToolbarMode()) {
6908+ } else {
65196909 DependencyManager::get<OffscreenUi>()->show(widgetUrl, name);
65206910 }
65216911 }
@@ -6569,7 +6959,7 @@ void Application::addAssetToWorldFromURL(QString url) { · 47 unmodified lines
65696959 } else {
65706960 filename.remove(".zip");
65716961 }
6572−
6962+
65736963 }
65746964
65756965 if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
@@ -6743,7 +7133,7 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q · 167 unmodified lines
67437133 addAssetToWorldError(filenameFromPath(filePath), errorInfo);
67447134 } else {
67457135 // to prevent files that aren't models or texture files from being loaded into world automatically
6746− if ((filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION)) ||
7136+ if ((filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION)) ||
67477137 ((filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) &&
67487138 ((!isBlocks) && (!isZip)))) {
67497139 addAssetToWorldAddEntity(filePath, mapping);
@@ -7137,7 +7527,6 @@ void Application::loadLODToolsDialog() { · 387 unmodified lines
71377527 }
71387528 }
71397529
7140−
71417530 void Application::loadEntityStatisticsDialog() {
71427531 auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
71437532 auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
@@ -7201,11 +7590,15 @@ void Application::loadAvatarBrowser() const { · 57 unmodified lines
72017590 void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
72027591 postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
72037592 // Get a screenshot and save it
7204− QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename);
7593+ QString path = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
7594+ TestScriptingInterface::getInstance()->getTestResultsLocation());
7595+
72057596 // If we're not doing an animated snapshot as well...
72067597 if (!includeAnimated) {
7207− // Tell the dependency manager that the capture of the still snapshot has taken place.
7208− emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
7598+ if (!path.isEmpty()) {
7599+ // Tell the dependency manager that the capture of the still snapshot has taken place.
7600+ emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
7601+ }
72097602 } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
72107603 // Get an animated GIF snapshot and save it
72117604 SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
@@ -7213,30 +7606,42 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa · 1 unmodified line
72137606 });
72147607 }
72157608
7216−void Application::takeSecondaryCameraSnapshot(const QString& filename) {
7217− postLambdaEvent([filename, this] {
7218− QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename);
7219− emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);
7609+void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
7610+ postLambdaEvent([notify, filename, this] {
7611+ QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
7612+ TestScriptingInterface::getInstance()->getTestResultsLocation());
7613+
7614+ emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
7615+ });
7616+}
7617+
7618+void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
7619+ postLambdaEvent([notify, filename, cubemapOutputFormat, cameraPosition] {
7620+ DependencyManager::get<Snapshot>()->save360Snapshot(cameraPosition, cubemapOutputFormat, notify, filename);
72207621 });
72217622 }
72227623
72237624 void Application::shareSnapshot(const QString& path, const QUrl& href) {
72247625 postLambdaEvent([path, href] {
72257626 // not much to do here, everything is done in snapshot code...
7226− Snapshot::uploadSnapshot(path, href);
7627+ DependencyManager::get<Snapshot>()->uploadSnapshot(path, href);
72277628 });
72287629 }
72297630
72307631 float Application::getRenderResolutionScale() const {
7231− if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) {
7632+ auto menu = Menu::getInstance();
7633+ if (!menu) {
7634+ return 1.0f;
7635+ }
7636+ if (menu->isOptionChecked(MenuOption::RenderResolutionOne)) {
72327637 return 1.0f;
7233− } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) {
7638+ } else if (menu->isOptionChecked(MenuOption::RenderResolutionTwoThird)) {
72347639 return 0.666f;
7235− } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) {
7640+ } else if (menu->isOptionChecked(MenuOption::RenderResolutionHalf)) {
72367641 return 0.5f;
7237− } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) {
7642+ } else if (menu->isOptionChecked(MenuOption::RenderResolutionThird)) {
72387643 return 0.333f;
7239− } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) {
7644+ } else if (menu->isOptionChecked(MenuOption::RenderResolutionQuarter)) {
72407645 return 0.25f;
72417646 } else {
72427647 return 1.0f;
@@ -7302,7 +7707,7 @@ void Application::windowMinimizedChanged(bool minimized) { · 59 unmodified lines
73027707 }
73037708 }
73047709
7305−void Application::postLambdaEvent(std::function<void()> f) {
7710+void Application::postLambdaEvent(const std::function<void()>& f) {
73067711 if (this->thread() == QThread::currentThread()) {
73077712 f();
73087713 } else {
@@ -7310,6 +7715,15 @@ void Application::postLambdaEvent(std::function<void()> f) { · 1 unmodified line
73107715 }
73117716 }
73127717
7718+void Application::sendLambdaEvent(const std::function<void()>& f) {
7719+ if (this->thread() == QThread::currentThread()) {
7720+ f();
7721+ } else {
7722+ LambdaEvent event(f);
7723+ QCoreApplication::sendEvent(this, &event);
7724+ }
7725+}
7726+
73137727 void Application::initPlugins(const QStringList& arguments) {
73147728 QCommandLineOption display("display", "Preferred displays", "displays");
73157729 QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays");
@@ -7392,8 +7806,8 @@ bool Application::isThrottleRendering() const { · 76 unmodified lines
73927806 bool Application::hasFocus() const {
73937807 bool result = (QApplication::activeWindow() != nullptr);
73947808 #if defined(Q_OS_WIN)
7395− // On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
7396− // take user focus away from their current window. So also check whether the application is the user's current foreground
7809+ // On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
7810+ // take user focus away from their current window. So also check whether the application is the user's current foreground
73977811 // window.
73987812 result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow();
73997813 #endif
@@ -7401,7 +7815,7 @@ bool Application::hasFocus() const { · 1 unmodified line
74017815 }
74027816
74037817 void Application::setFocus() {
7404− // Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
7818+ // Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
74057819 // flashes the taskbar icon.
74067820 auto window = qApp->getWindow();
74077821 window->activateWindow();
@@ -7451,7 +7865,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const { · 43 unmodified lines
74517865
74527866 static const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
74537867
7454−static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) {
7868+static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active) {
74557869 auto menu = Menu::getInstance();
74567870 QString name = displayPlugin->getName();
74577871 auto grouping = displayPlugin->getGrouping();
@@ -7478,7 +7892,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti · 20 unmodified lines
74787892 }
74797893 auto parent = menu->getMenu(MenuOption::OutputMenu);
74807894 auto action = menu->addActionToQMenuAndActionHash(parent,
7481− name, 0, qApp,
7895+ name, QKeySequence(Qt::CTRL + (Qt::Key_0 + index)), qApp,
74827896 SLOT(updateDisplayMode()),
74837897 QAction::NoRole, Menu::UNSPECIFIED_POSITION, groupingMenu);
74847898
@@ -7496,66 +7910,24 @@ void Application::updateDisplayMode() { · 11 unmodified lines
74967910 qFatal("Attempted to switch display plugins from a non-main thread");
74977911 }
74987912
7499− auto menu = Menu::getInstance();
7500− auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
7501−
7502− static std::once_flag once;
7503− std::call_once(once, [&] {
7504− bool first = true;
7505−
7506− // first sort the plugins into groupings: standard, advanced, developer
7507− DisplayPluginList standard;
7508− DisplayPluginList advanced;
7509− DisplayPluginList developer;
7510− foreach(auto displayPlugin, displayPlugins) {
7511− displayPlugin->setContext(_gpuContext);
7512− auto grouping = displayPlugin->getGrouping();
7513− switch (grouping) {
7514− case Plugin::ADVANCED:
7515− advanced.push_back(displayPlugin);
7516− break;
7517− case Plugin::DEVELOPER:
7518− developer.push_back(displayPlugin);
7519− break;
7520− default:
7521− standard.push_back(displayPlugin);
7522− break;
7523− }
7524− }
7525−
7526− // concatenate the groupings into a single list in the order: standard, advanced, developer
7527− standard.insert(std::end(standard), std::begin(advanced), std::end(advanced));
7528− standard.insert(std::end(standard), std::begin(developer), std::end(developer));
7529−
7530− foreach(auto displayPlugin, standard) {
7531− addDisplayPluginToMenu(displayPlugin, first);
7532− auto displayPluginName = displayPlugin->getName();
7533− QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
7534− resizeGL();
7535− });
7536− QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
7537− first = false;
7538− }
7539−
7540− // after all plugins have been added to the menu, add a separator to the menu
7541− auto menu = Menu::getInstance();
7542− auto parent = menu->getMenu(MenuOption::OutputMenu);
7543− parent->addSeparator();
7544− });
7545−
7913+ // Once time initialization code that depends on the UI being available
7914+ auto displayPlugins = getDisplayPlugins();
75467915
75477916 // Default to the first item on the list, in case none of the menu items match
75487917 DisplayPluginPointer newDisplayPlugin = displayPlugins.at(0);
7549− foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
7550− QString name = displayPlugin->getName();
7551− QAction* action = menu->getActionForOption(name);
7552− // Menu might have been removed if the display plugin lost
7553− if (!action) {
7554− continue;
7555− }
7556− if (action->isChecked()) {
7557− newDisplayPlugin = displayPlugin;
7558− break;
7918+ auto menu = getPrimaryMenu();
7919+ if (menu) {
7920+ foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
7921+ QString name = displayPlugin->getName();
7922+ QAction* action = menu->getActionForOption(name);
7923+ // Menu might have been removed if the display plugin lost
7924+ if (!action) {
7925+ continue;
7926+ }
7927+ if (action->isChecked()) {
7928+ newDisplayPlugin = displayPlugin;
7929+ break;
7930+ }
75597931 }
75607932 }
75617933
@@ -7563,8 +7935,21 @@ void Application::updateDisplayMode() { · 1 unmodified line
75637935 return;
75647936 }
75657937
7938+ setDisplayPlugin(newDisplayPlugin);
7939+}
7940+
7941+void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
7942+ if (newDisplayPlugin == _displayPlugin) {
7943+ return;
7944+ }
7945+
7946+ // FIXME don't have the application directly set the state of the UI,
7947+ // instead emit a signal that the display plugin is changing and let
7948+ // the desktop lock itself. Reduces coupling between the UI and display
7949+ // plugins
75667950 auto offscreenUi = DependencyManager::get<OffscreenUi>();
75677951 auto desktop = offscreenUi->getDesktop();
7952+ auto menu = Menu::getInstance();
75687953
75697954 // Make the switch atomic from the perspective of other threads
75707955 {
@@ -7572,8 +7957,8 @@ void Application::updateDisplayMode() { · 1 unmodified line
75727957 bool wasRepositionLocked = false;
75737958 if (desktop) {
75747959 // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
7575− wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
7576− offscreenUi->getDesktop()->setProperty("repositionLocked", true);
7960+ wasRepositionLocked = desktop->property("repositionLocked").toBool();
7961+ desktop->setProperty("repositionLocked", true);
75777962 }
75787963
75797964 if (_displayPlugin) {
@@ -7585,6 +7970,8 @@ void Application::updateDisplayMode() { · 5 unmodified lines
75857970 bool active = newDisplayPlugin->activate();
75867971
75877972 if (!active) {
7973+ auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
7974+
75887975 // If the new plugin fails to activate, fallback to last display
75897976 qWarning() << "Failed to activate display: " << newDisplayPlugin->getName();
75907977 newDisplayPlugin = oldDisplayPlugin;
@@ -7605,13 +7992,6 @@ void Application::updateDisplayMode() { · 14 unmodified lines
76057992 if (!active) {
76067993 qFatal("Failed to activate fallback plugin");
76077994 }
7608−
7609− // We've changed the selection - it should be reflected in the menu
7610− QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
7611− if (!action) {
7612− qFatal("Failed to find activated plugin");
7613− }
7614− action->setChecked(true);
76157995 }
76167996
76177997 offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
@@ -7638,14 +8018,21 @@ void Application::updateDisplayMode() { · 20 unmodified lines
76388018 getMyAvatar()->reset(false);
76398019
76408020 // switch to first person if entering hmd and setting is checked
7641− if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
7642− menu->setIsOptionChecked(MenuOption::FirstPerson, true);
7643− cameraMenuChanged();
8021+ if (menu) {
8022+ QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
8023+ if (action) {
8024+ action->setChecked(true);
8025+ }
8026+
8027+ if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
8028+ menu->setIsOptionChecked(MenuOption::FirstPerson, true);
8029+ cameraMenuChanged();
8030+ }
8031+
8032+ // Remove the mirror camera option from menu if in HMD mode
8033+ auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
8034+ mirrorAction->setVisible(!isHmd);
76448035 }
7645−
7646− // Remove the mirror camera option from menu if in HMD mode
7647− auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
7648− mirrorAction->setVisible(!isHmd);
76498036
76508037 Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
76518038 }
@@ -7666,11 +8053,30 @@ void Application::switchDisplayMode() { · 14 unmodified lines
76668053 setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME);
76678054 startHMDStandBySession();
76688055 }
7669− emit activeDisplayPluginChanged();
76708056 }
76718057 _previousHMDWornStatus = currentHMDWornStatus;
76728058 }
76738059
8060+void Application::setShowBulletWireframe(bool value) {
8061+ _physicsEngine->setShowBulletWireframe(value);
8062+}
8063+
8064+void Application::setShowBulletAABBs(bool value) {
8065+ _physicsEngine->setShowBulletAABBs(value);
8066+}
8067+
8068+void Application::setShowBulletContactPoints(bool value) {
8069+ _physicsEngine->setShowBulletContactPoints(value);
8070+}
8071+
8072+void Application::setShowBulletConstraints(bool value) {
8073+ _physicsEngine->setShowBulletConstraints(value);
8074+}
8075+
8076+void Application::setShowBulletConstraintLimits(bool value) {
8077+ _physicsEngine->setShowBulletConstraintLimits(value);
8078+}
8079+
76748080 void Application::startHMDStandBySession() {
76758081 _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession();
76768082 }
@@ -7721,15 +8127,18 @@ void Application::unresponsiveApplication() { · 44 unmodified lines
77218127 }
77228128
77238129 void Application::setActiveDisplayPlugin(const QString& pluginName) {
7724− auto menu = Menu::getInstance();
7725− foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
8130+ DisplayPluginPointer newDisplayPlugin;
8131+ for (DisplayPluginPointer displayPlugin : PluginManager::getInstance()->getDisplayPlugins()) {
77268132 QString name = displayPlugin->getName();
7727− QAction* action = menu->getActionForOption(name);
77288133 if (pluginName == name) {
7729− action->setChecked(true);
8134+ newDisplayPlugin = displayPlugin;
8135+ break;
77308136 }
77318137 }
7732− updateDisplayMode();
8138+
8139+ if (newDisplayPlugin) {
8140+ setDisplayPlugin(newDisplayPlugin);
8141+ }
77338142 }
77348143
77358144 void Application::handleLocalServerConnection() const {
@@ -7760,7 +8169,6 @@ void Application::readArgumentsFromLocalSocket() const { · 24 unmodified lines
77608169 }
77618170
77628171 void Application::showDesktop() {
7763− Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true);
77648172 }
77658173
77668174 CompositorHelper& Application::getApplicationCompositor() const {
@@ -7849,4 +8257,22 @@ void Application::saveNextPhysicsStats(QString filename) { · 82 unmodified lines
78498257 _physicsEngine->saveNextPhysicsStats(filename);
78508258 }
78518259
8260+#if defined(Q_OS_ANDROID)
8261+void Application::enterBackground() {
8262+ QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
8263+ "stop", Qt::BlockingQueuedConnection);
8264+ if (getActiveDisplayPlugin()->isActive()) {
8265+ getActiveDisplayPlugin()->deactivate();
8266+ }
8267+}
8268+
8269+void Application::enterForeground() {
8270+ QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
8271+ "start", Qt::BlockingQueuedConnection);
8272+ if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
8273+ qWarning() << "Could not re-activate display plugin";
8274+ }
8275+}
8276+#endif
8277+
78528278 #include "Application.moc"