Showing the changes to interface/src/Application.cpp (1 of 24 files). Show all
Unified · Split
| @@ -349,19 +349,14 @@ public: · 348 unmodified lines | ||
| 349 | 349 | }; |
| 350 | 350 | #endif |
| 351 | 351 | |
| 352 | −enum CustomEventTypes { | |
| 353 | − Lambda = QEvent::User + 1, | |
| 354 | − Paint = Lambda + 1, | |
| 355 | −}; | |
| 356 | − | |
| 357 | 352 | class LambdaEvent : public QEvent { |
| 358 | 353 | std::function<void()> _fun; |
| 359 | 354 | public: |
| 360 | 355 | LambdaEvent(const std::function<void()> & fun) : |
| 361 | − QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) { | |
| 356 | + QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) { | |
| 362 | 357 | } |
| 363 | 358 | LambdaEvent(std::function<void()> && fun) : |
| 364 | − QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) { | |
| 359 | + QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) { | |
| 365 | 360 | } |
| 366 | 361 | void call() const { _fun(); } |
| 367 | 362 | }; |
| @@ -1062,18 +1057,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : · 694 unmodified lines | ||
| 1062 | 1057 | connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); |
| 1063 | 1058 | qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); |
| 1064 | 1059 | |
| 1065 | − _idleTimer = new QTimer(this); | |
| 1066 | − connect(_idleTimer, &QTimer::timeout, [=] { | |
| 1067 | − idle(usecTimestampNow()); | |
| 1068 | − }); | |
| 1069 | − connect(this, &Application::beforeAboutToQuit, [=] { | |
| 1070 | − disconnect(_idleTimer); | |
| 1071 | − }); | |
| 1072 | − // Setting the interval to zero forces this to get called whenever there are no messages | |
| 1073 | − // in the queue, which can be pretty damn frequent. Hence the idle function has a bunch | |
| 1074 | − // of logic to abort early if it's being called too often. | |
| 1075 | − _idleTimer->start(0); | |
| 1076 | − | |
| 1077 | 1060 | // After all of the constructor is completed, then set firstRun to false. |
| 1078 | 1061 | Setting::Handle<bool> firstRun{ Settings::firstRun, true }; |
| 1079 | 1062 | firstRun.set(false); |
| @@ -1161,10 +1144,16 @@ void Application::cleanupBeforeQuit() { · 81 unmodified lines | ||
| 1161 | 1144 | |
| 1162 | 1145 | getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts |
| 1163 | 1146 | |
| 1147 | + // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) | |
| 1148 | + QThreadPool::globalInstance()->clear(); | |
| 1149 | + | |
| 1164 | 1150 | DependencyManager::get<ScriptEngines>()->saveScripts(); |
| 1165 | 1151 | DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts |
| 1166 | 1152 | DependencyManager::destroy<ScriptEngines>(); |
| 1167 | 1153 | |
| 1154 | + // Cleanup all overlays after the scripts, as scripts might add more | |
| 1155 | + _overlays.cleanupAllOverlays(); | |
| 1156 | + | |
| 1168 | 1157 | // first stop all timers directly or by invokeMethod |
| 1169 | 1158 | // depending on what thread they run in |
| 1170 | 1159 | locationUpdateTimer.stop(); |
| @@ -1440,23 +1429,15 @@ void Application::initializeUi() { · 269 unmodified lines | ||
| 1440 | 1429 | }); |
| 1441 | 1430 | } |
| 1442 | 1431 | |
| 1443 | − | |
| 1444 | 1432 | void Application::paintGL() { |
| 1445 | − updateHeartbeat(); | |
| 1446 | − // Some plugins process message events, potentially leading to | |
| 1447 | − // re-entering a paint event. don't allow further processing if this | |
| 1448 | − // happens | |
| 1449 | − if (_inPaint) { | |
| 1433 | + // Some plugins process message events, allowing paintGL to be called reentrantly. | |
| 1434 | + if (_inPaint || _aboutToQuit) { | |
| 1450 | 1435 | return; |
| 1451 | 1436 | } |
| 1437 | + | |
| 1452 | 1438 | _inPaint = true; |
| 1453 | − Finally clearFlagLambda([this] { _inPaint = false; }); | |
| 1439 | + Finally clearFlag([this] { _inPaint = false; }); | |
| 1454 | 1440 | |
| 1455 | − // paintGL uses a queued connection, so we can get messages from the queue even after we've quit | |
| 1456 | − // and the plugins have shutdown | |
| 1457 | − if (_aboutToQuit) { | |
| 1458 | − return; | |
| 1459 | − } | |
| 1460 | 1441 | _frameCount++; |
| 1461 | 1442 | _frameCounter.increment(); |
| 1462 | 1443 | |
| @@ -1814,13 +1795,30 @@ bool Application::event(QEvent* event) { · 351 unmodified lines | ||
| 1814 | 1795 | return false; |
| 1815 | 1796 | } |
| 1816 | 1797 | |
| 1817 | − if ((int)event->type() == (int)Lambda) { | |
| 1818 | − static_cast<LambdaEvent*>(event)->call(); | |
| 1798 | + static bool justPresented = false; | |
| 1799 | + if ((int)event->type() == (int)Present) { | |
| 1800 | + if (justPresented) { | |
| 1801 | + justPresented = false; | |
| 1802 | + | |
| 1803 | + // If presentation is hogging the main thread, repost as low priority to avoid hanging the GUI. | |
| 1804 | + // This has the effect of allowing presentation to exceed the paint budget by X times and | |
| 1805 | + // only dropping every (1/X) frames, instead of every ceil(X) frames. | |
| 1806 | + // (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS). | |
| 1807 | + removePostedEvents(this, Present); | |
| 1808 | + postEvent(this, new QEvent(static_cast<QEvent::Type>(Present)), Qt::LowEventPriority); | |
| 1809 | + return true; | |
| 1810 | + } | |
| 1811 | + | |
| 1812 | + idle(); | |
| 1813 | + return true; | |
| 1814 | + } else if ((int)event->type() == (int)Paint) { | |
| 1815 | + justPresented = true; | |
| 1816 | + paintGL(); | |
| 1819 | 1817 | return true; |
| 1820 | 1818 | } |
| 1821 | 1819 | |
| 1822 | − if ((int)event->type() == (int)Paint) { | |
| 1823 | − paintGL(); | |
| 1820 | + if ((int)event->type() == (int)Lambda) { | |
| 1821 | + static_cast<LambdaEvent*>(event)->call(); | |
| 1824 | 1822 | return true; |
| 1825 | 1823 | } |
| 1826 | 1824 | |
| @@ -2598,72 +2596,63 @@ bool Application::acceptSnapshot(const QString& urlString) { · 771 unmodified lines | ||
| 2598 | 2596 | |
| 2599 | 2597 | static uint32_t _renderedFrameIndex { INVALID_FRAME }; |
| 2600 | 2598 | |
| 2601 | −void Application::idle(uint64_t now) { | |
| 2602 | − // NOTICE NOTICE NOTICE NOTICE | |
| 2603 | − // Do not insert new code between here and the PROFILE_RANGE declaration | |
| 2604 | − // unless you know exactly what you're doing. This idle function can be | |
| 2605 | − // called thousands per second or more, so any additional work that's done | |
| 2606 | − // here will have a serious impact on CPU usage. Only add code after all | |
| 2607 | − // the thottling logic, i.e. after PROFILE_RANGE | |
| 2608 | − // NOTICE NOTICE NOTICE NOTICE | |
| 2609 | − updateHeartbeat(); | |
| 2610 | − | |
| 2611 | − if (_aboutToQuit || _inPaint) { | |
| 2612 | − return; // bail early, nothing to do here. | |
| 2599 | +void Application::idle() { | |
| 2600 | + // idle is called on a queued connection, so make sure we should be here. | |
| 2601 | + if (_inPaint || _aboutToQuit) { | |
| 2602 | + return; | |
| 2613 | 2603 | } |
| 2614 | 2604 | |
| 2615 | 2605 | auto displayPlugin = getActiveDisplayPlugin(); |
| 2616 | − // depending on whether we're throttling or not. | |
| 2617 | − // Once rendering is off on another thread we should be able to have Application::idle run at start(0) in | |
| 2618 | − // perpetuity and not expect events to get backed up. | |
| 2619 | − bool isThrottled = displayPlugin->isThrottled(); | |
| 2620 | − // Only run simulation code if more than the targetFramePeriod have passed since last time we ran | |
| 2621 | − // This attempts to lock the simulation at 60 updates per second, regardless of framerate | |
| 2622 | − float timeSinceLastUpdateUs = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC; | |
| 2623 | − float secondsSinceLastUpdate = timeSinceLastUpdateUs / USECS_PER_SECOND; | |
| 2624 | 2606 | |
| 2625 | − if (isThrottled && (timeSinceLastUpdateUs / USECS_PER_MSEC) < THROTTLED_SIM_FRAME_PERIOD_MS) { | |
| 2626 | − // Throttling both rendering and idle | |
| 2627 | − return; // bail early, we're throttled and not enough time has elapsed | |
| 2628 | − } | |
| 2607 | +#ifdef DEBUG_PAINT_DELAY | |
| 2608 | + static uint64_t paintDelaySamples{ 0 }; | |
| 2609 | + static uint64_t paintDelayUsecs{ 0 }; | |
| 2610 | + | |
| 2611 | + paintDelayUsecs += displayPlugin->getPaintDelayUsecs(); | |
| 2629 | 2612 | |
| 2630 | − auto presentCount = displayPlugin->presentCount(); | |
| 2631 | − if (presentCount < _renderedFrameIndex) { | |
| 2632 | − _renderedFrameIndex = INVALID_FRAME; | |
| 2613 | + static const int PAINT_DELAY_THROTTLE = 1000; | |
| 2614 | + if (++paintDelaySamples % PAINT_DELAY_THROTTLE == 0) { | |
| 2615 | + qCDebug(interfaceapp).nospace() << | |
| 2616 | + "Paint delay (" << paintDelaySamples << " samples): " << | |
| 2617 | + (float)paintDelaySamples / paintDelayUsecs << "us"; | |
| 2633 | 2618 | } |
| 2619 | +#endif | |
| 2634 | 2620 | |
| 2635 | − // Don't saturate the main thread with rendering and simulation, | |
| 2636 | − // unless display plugin has increased by at least one frame | |
| 2637 | − if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) { | |
| 2638 | − // Record what present frame we're on | |
| 2639 | − _renderedFrameIndex = presentCount; | |
| 2621 | + float msecondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC / USECS_PER_MSEC; | |
| 2640 | 2622 | |
| 2641 | − // request a paint, get to it as soon as possible: high priority | |
| 2642 | − postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority); | |
| 2643 | − } else { | |
| 2644 | − // there's no use in simulating or rendering faster then the present rate. | |
| 2623 | + // Throttle if requested | |
| 2624 | + if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) { | |
| 2645 | 2625 | return; |
| 2646 | 2626 | } |
| 2647 | 2627 | |
| 2648 | − // NOTICE NOTICE NOTICE NOTICE | |
| 2649 | − // do NOT add new code above this line unless you want it to be executed potentially | |
| 2650 | − // thousands of times per second | |
| 2651 | − // NOTICE NOTICE NOTICE NOTICE | |
| 2628 | + // Sync up the _renderedFrameIndex | |
| 2629 | + _renderedFrameIndex = displayPlugin->presentCount(); | |
| 2652 | 2630 | |
| 2653 | − PROFILE_RANGE(__FUNCTION__); | |
| 2631 | + // Request a paint ASAP | |
| 2632 | + postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority + 1); | |
| 2633 | + | |
| 2634 | + // Update the deadlock watchdog | |
| 2635 | + updateHeartbeat(); | |
| 2636 | + | |
| 2637 | + auto offscreenUi = DependencyManager::get<OffscreenUi>(); | |
| 2654 | 2638 | |
| 2655 | 2639 | // These tasks need to be done on our first idle, because we don't want the showing of |
| 2656 | 2640 | // overlay subwindows to do a showDesktop() until after the first time through |
| 2657 | 2641 | static bool firstIdle = true; |
| 2658 | 2642 | if (firstIdle) { |
| 2659 | 2643 | firstIdle = false; |
| 2660 | − auto offscreenUi = DependencyManager::get<OffscreenUi>(); | |
| 2661 | 2644 | connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); |
| 2662 | 2645 | _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); |
| 2646 | + } else { | |
| 2647 | + // FIXME: AvatarInputs are positioned incorrectly if instantiated before the first paint | |
| 2648 | + AvatarInputs::getInstance()->update(); | |
| 2663 | 2649 | } |
| 2664 | 2650 | |
| 2651 | + PROFILE_RANGE(__FUNCTION__); | |
| 2652 | + | |
| 2653 | + float secondsSinceLastUpdate = msecondsSinceLastUpdate / MSECS_PER_SECOND; | |
| 2654 | + | |
| 2665 | 2655 | // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. |
| 2666 | − auto offscreenUi = DependencyManager::get<OffscreenUi>(); | |
| 2667 | 2656 | if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) { |
| 2668 | 2657 | _keyboardMouseDevice->pluginFocusOutEvent(); |
| 2669 | 2658 | _keyboardDeviceHasFocus = false; |
| @@ -2679,7 +2668,6 @@ void Application::idle(uint64_t now) { · 9 unmodified lines | ||
| 2679 | 2668 | checkChangeCursor(); |
| 2680 | 2669 | |
| 2681 | 2670 | Stats::getInstance()->updateStats(); |
| 2682 | − AvatarInputs::getInstance()->update(); | |
| 2683 | 2671 | |
| 2684 | 2672 | _simCounter.increment(); |
| 2685 | 2673 | |