From 42a1ee353e2acddf174ba057c65a9aa299e9646b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 18 Nov 2015 14:09:33 -0800 Subject: [PATCH] checkpoint --- interface/src/Application.cpp | 25 +++++---- interface/src/avatar/AvatarManager.cpp | 53 +++++++++---------- libraries/shared/src/PIDController.cpp | 72 ++++++++++++++------------ libraries/shared/src/PIDController.h | 2 + 4 files changed, 82 insertions(+), 70 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f33cd763ca..2eaf898e72 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1095,6 +1095,21 @@ void Application::paintGL() { _inPaint = true; Finally clearFlagLambda([this] { _inPaint = false; }); + _lastInstantaneousFps = instantaneousFps; + // Time between previous paintGL call and this one, which can vary not only with vSync misses, but also with QT timing. + // This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second. + // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. + const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize + const float targetPeriod = isHMDMode() ? 1.0f / 75.0f : 1.0f / 60.0f; + const float nSyncsByFrameRate = round(actualPeriod / targetPeriod); + const float accuracyAllowance = 0.0005f; // sometimes paint goes over and it isn't reflected in actualPeriod + const float nSyncsByPaintWait = floor((_lastPaintWait + accuracyAllowance) / targetPeriod); + const float nSyncs = nSyncsByFrameRate + nSyncsByPaintWait; + const float modularPeriod = ((nSyncs - 1) * targetPeriod) + actualPeriod; + const float deducedNonVSyncPeriod = modularPeriod - _lastPaintWait; + _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; + + auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); _offscreenContext->makeCurrent(); @@ -1342,18 +1357,8 @@ void Application::paintGL() { displayPlugin->finishFrame(); } uint64_t displayEnd = usecTimestampNow(); - // Store together, without Application::idle happening in between setting fps and period. - _lastInstantaneousFps = instantaneousFps; const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs _lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND; - const float targetPeriod = isHMDMode() ? 1.0f/75.0f : 1.0f/60.0f; - const float actualPeriod = 1.0f / instantaneousFps; - const float nSyncsByFrameRate = round(actualPeriod / targetPeriod); - const float accuracyAllowance = 0.0005; - const float nSyncsByPaintWait = floor((_lastPaintWait + accuracyAllowance) / targetPeriod); // sometimes paint goes over and it isn't reflected in actualPeriod - const float modularPeriod = (nSyncsByFrameRate + nSyncsByPaintWait) * targetPeriod; - const float deducedNonVSyncPeriod = modularPeriod - _lastPaintWait; - _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b2a2e27999..18e4dd40be 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -61,34 +61,12 @@ void AvatarManager::registerMetaTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); } -#define TARGET_FPS 60.0f -#define TARGET_PERIOD_MS (1000.0f / TARGET_FPS) AvatarManager::AvatarManager(QObject* parent) : _avatarFades() { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType >("NodeWeakPointer"); _myAvatar = std::make_shared(std::make_shared()); - _renderDistanceController.setMeasuredValueSetpoint(TARGET_FPS); //FIXME - const float TREE_SCALE = 32768.0f; // Not in shared library, alas. - const float SMALLEST_REASONABLE_HORIZON = 0.5f; // FIXME 5 - _renderDistanceController.setControlledValueHighLimit(1.0f/SMALLEST_REASONABLE_HORIZON); - _renderDistanceController.setControlledValueLowLimit(1.0f/TREE_SCALE); - - // Advice for tuning parameters: - // See PIDController.h. There's a sectionon tuning in the reference. - // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. - // Turn on logging with the following: - _renderDistanceController.setHistorySize("avatar render", TARGET_FPS * 4); // FIXME - // KP is usually tuned by setting the other constants to zero, finding the maximum value that doesn't oscillate, - // and taking about 0.6 of that. A typical osciallation would be with error=37fps with avatars 10m away, so - // KP*37=1/10 => KP(oscillating)=0.1/37 = 0.0027 - _renderDistanceController.setKP(0.0012f); - // alt: - // Our anti-windup limits accumulated error to 10*targetFrameRate, so the sanity check on KI is - // KI*750=controlledValueHighLimit=1 => KI=1/750. - _renderDistanceController.setKI(0.001); - _renderDistanceController.setKD(0.0001); // a touch of kd increases the speed by which we get there auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); @@ -112,6 +90,29 @@ void AvatarManager::init() { _myAvatar->addToScene(_myAvatar, scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); + + const float target_fps = 75.0f; // qApp->isHMDMode() ? 75.0f : 60.0f; + _renderDistanceController.setMeasuredValueSetpoint(target_fps); + const float TREE_SCALE = 32768.0f; // Not in shared library, alas. + const float SMALLEST_REASONABLE_HORIZON = 0.5f; // FIXME 5 + _renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON); + _renderDistanceController.setControlledValueLowLimit(1.0f / TREE_SCALE); + + // Advice for tuning parameters: + // See PIDController.h. There's a sectionon tuning in the reference. + // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. + // Turn on logging with the following: + _renderDistanceController.setHistorySize("avatar render", target_fps * 4); // FIXME + // KP is usually tuned by setting the other constants to zero, finding the maximum value that doesn't oscillate, + // and taking about 0.6 of that. A typical osciallation would be with error=37fps with avatars 10m away, so + // KP*37=1/10 => KP(oscillating)=0.1/37 = 0.0027 + _renderDistanceController.setKP(0.0015f); + // alt: + // Our anti-windup limits accumulated error to 10*targetFrameRate, so the sanity check on KI is + // KI*750=controlledValueHighLimit=1 => KI=1/750. + _renderDistanceController.setKI(0.001f); + _renderDistanceController.setKD(0.0001f); // a touch of kd increases the speed by which we get there + } void AvatarManager::updateMyAvatar(float deltaTime) { @@ -142,14 +143,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const float FEED_FORWARD_RANGE = 2; const float fps = qApp->getLastInstanteousFps(); const float paintWait = qApp->getLastPaintWait(); - //const float modularizedPeriod = floor((1000.0f / std::min(fps, TARGET_FPS)) / TARGET_PERIOD_MS) * TARGET_PERIOD_MS; - // measured value: 1) bigger => more desirable plant activity (i.e., more rendering), 2) setpoint=TARGET_PERIOD_MS=13.333 - // single vsync: no load=>1or2. high load=>12or13 - // over vsync: just over: 13. way over: 14...15...16 - //const float effective = ((1000.0f / fps) < TARGET_PERIOD_MS) ? (TARGET_PERIOD_MS - paintWait) : ((2.0f * TARGET_PERIOD_MS) - paintWait); const float deduced = qApp->getLastDeducedNonVSyncFps(); const bool isAtSetpoint = false; //FIXME fabsf(effectiveFps - _renderDistanceController.getMeasuredValueSetpoint()) < FEED_FORWARD_RANGE; - const float distance = 1.0f / _renderDistanceController.update(deduced + (isAtSetpoint ? _renderFeedForward : 0.0f), deltaTime, isAtSetpoint, fps, paintWait); + //const float distance = 1.0f / _renderDistanceController.update(deduced + (isAtSetpoint ? _renderFeedForward : 0.0f), deltaTime, isAtSetpoint, fps, paintWait); + const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, isAtSetpoint, fps, paintWait); const float RENDER_DISTANCE_DEADBAND = 0.0f; //FIXME 0.3f; // meters if (fabsf(distance - _renderDistance) > RENDER_DISTANCE_DEADBAND) { diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index 42447e9809..2756ab615f 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -31,41 +31,49 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator getControlledValueLowLimit(), getControlledValueHighLimit()); - if (_history.capacity()) { // THIS SECTION ONLY FOR LOGGING - // Don't report each update(), as the I/O messes with the results a lot. - // Instead, add to history, and then dump out at once when full. - // Typically, the first few values reported in each batch should be ignored. - const int n = _history.size(); - _history.resize(n + 1); - Row& next = _history[n]; - next.measured = measuredValue; - next.FIXME1 = FIXME1; - next.FIXME2 = FIXME2; - next.dt = dt; - next.error = error; - next.accumulated = accumulatedError; - next.changed = changeInError; - next.p = p; - next.i = i; - next.d = d; - next.computed = computedValue; - if (_history.size() == _history.capacity()) { // report when buffer is full - qCDebug(shared) << _label << "measured dt FIXME || error accumulated changed || p i d controlled"; - for (int i = 0; i < _history.size(); i++) { - Row& row = _history[i]; - qCDebug(shared) << row.measured << row.dt << row.FIXME1 << row.FIXME2 << - "||" << row.error << row.accumulated << row.changed << - "||" << row.p << row.i << row.d << row.computed; - } - qCDebug(shared) << "Limits: accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << - "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << - "kp/ki/kd/bias" << getKP() << getKI() << getKD() << getBias(); - _history.resize(0); - } - } + if (_history.capacity()) { // if logging/reporting + updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue, FIXME1, FIXME2); + } // update state for next time _lastError = error; _lastAccumulation = accumulatedError; return computedValue; +} + +// Just for logging/reporting. Used when picking/verifying the operational parameters. +void PIDController::updateHistory(float measuredValue, float dt, float error, float accumulatedError, float changeInError, float p, float i, float d, float computedValue, float FIXME1, float FIXME2) { + // Don't report each update(), as the I/O messes with the results a lot. + // Instead, add to history, and then dump out at once when full. + // Typically, the first few values reported in each batch should be ignored. + const int n = _history.size(); + _history.resize(n + 1); + Row& next = _history[n]; + next.measured = measuredValue; + next.FIXME1 = FIXME1; + next.FIXME2 = FIXME2; + next.dt = dt; + next.error = error; + next.accumulated = accumulatedError; + next.changed = changeInError; + next.p = p; + next.i = i; + next.d = d; + next.computed = computedValue; + if (_history.size() == _history.capacity()) { // report when buffer is full + reportHistory(); + _history.resize(0); + } +} +void PIDController::reportHistory() { + qCDebug(shared) << _label << "measured dt FIXME || error accumulated changed || p i d controlled"; + for (int i = 0; i < _history.size(); i++) { + Row& row = _history[i]; + qCDebug(shared) << row.measured << (row.dt * 1000) << row.FIXME1 << (row.FIXME2 * 1000) << + "||" << row.error << row.accumulated << row.changed << + "||" << row.p << row.i << row.d << row.computed; + } + qCDebug(shared) << "Limits: setpoint" << getMeasuredValueSetpoint() << "accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << + "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << + "kp/ki/kd/bias" << getKP() << getKI() << getKD() << getBias(); } \ No newline at end of file diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index dd0c4a8528..a2adbf98ab 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -70,6 +70,8 @@ public: float computed; }; protected: + void reportHistory(); + void updateHistory(float measured, float dt, float error, float accumulatedError, float changeInErro, float p, float i, float d, float computedValue, float FIXME1, float FIXME2); float _measuredValueSetpoint { 0.0f }; float _controlledValueLowLimit { 0.0f }; float _controlledValueHighLimit { std::numeric_limits::max() };