410 lines · 15.6 KB · C++
RawDownload
////  LODManager.cpp//  interface/src/LODManager.h////  Created by Clement on 1/16/15.//  Copyright 2015 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "LODManager.h" #include <SettingHandle.h>#include <OctreeUtils.h>#include <Util.h> #include "Application.h"#include "ui/DialogsManager.h"#include "InterfaceLogging.h" const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS;const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS);Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS); LODManager::LODManager() {} // We use a "time-weighted running average" of the maxRenderTime and compare it against min/max thresholds// to determine if we should adjust the level of detail (LOD).//// A time-weighted running average has a timescale which determines how fast the average tracks the measured// value in real-time.  Given a step-function in the mesured value, and assuming measurements happen// faster than the runningAverage is computed, the error between the value and its runningAverage will be// reduced by 1/e every timescale of real-time that passes.const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec // batchTIme is always contained in presentTime.// We favor using batchTime instead of presentTime as a representative value for rendering duration (on present thread) // if batchTime + cushionTime < presentTime.// since we are shooting for fps around 60, 90Hz, the ideal frames are around 10ms// so we are picking a cushion time of 3ms const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 3.0f; // msec void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) {    // Make sure the sampled time are positive values    _presentTime = std::max(0.0f, presentTime);    _engineRunTime = std::max(0.0f, engineRunTime);    _batchTime = std::max(0.0f, batchTime);    _gpuTime = std::max(0.0f, gpuTime);} void LODManager::autoAdjustLOD(float realTimeDelta) {     // The "render time" is the worse of:    // - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N    // - batchTime: Time spent in the present thread processing the batches of gpu::Frame N+1    // - presentTime: Time spent in the present thread between the last 2 swap buffers considered the total time to submit gpu::Frame N+1    // - gpuTime: Time spent in the GPU executing the gpu::Frame N + 2     // But Present time is in reality synched with the monitor/display refresh rate, it s always longer than batchTime.    // So if batchTime is fast enough relative to presentTime we are using it, otherwise we are using presentTime. got it ?    auto presentTime = (_presentTime > _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME ? _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME : _presentTime);    float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime);     // maxRenderTime must be a realistic valid duration in order for the regulation to work correctly.    // We make sure it s a non zero positive value (1.0ms) under 1 sec    maxRenderTime = std::max(1.0f, std::min(maxRenderTime, (float)MSECS_PER_SECOND));     // realTimeDelta must be a realistic valid duration in order for the regulation to work correctly.    // We make sure it a positive value under 1 sec    // note that if real time delta is very small we will early exit to avoid division by zero    realTimeDelta = std::max(0.0f, std::min(realTimeDelta, 1.0f));     // compute time-weighted running average render time (now and smooth)    // We MUST clamp the blend between 0.0 and 1.0 for stability    float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f;    float smoothBlend = (realTimeDelta <  LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f;     //Evaluate the running averages for the render time    // We must sanity check for the output average evaluated to be in a valid range to avoid issues     _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec    _nowRenderTime = std::max(0.0f, std::min(_nowRenderTime, (float)MSECS_PER_SECOND));    _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec    _smoothRenderTime = std::max(0.0f, std::min(_smoothRenderTime, (float)MSECS_PER_SECOND));     // Early exit if not regulating or if the simulation or render times don't matter    if (!_automaticLODAdjust || realTimeDelta <= 0.0f || _nowRenderTime <= 0.0f || _smoothRenderTime <= 0.0f) {        return;    }     // Previous values for output    float oldOctreeSizeScale = getOctreeSizeScale();    float oldLODAngle = getLODAngleDeg();     // Target fps is slightly overshooted by 5hz    float targetFPS = getLODTargetFPS() + LOD_OFFSET_FPS;     // Current fps based on latest measurments    float currentNowFPS = (float)MSECS_PER_SECOND / _nowRenderTime;    float currentSmoothFPS = (float)MSECS_PER_SECOND / _smoothRenderTime;     // Compute the Variance of the FPS signal (FPS - smouthFPS)^2    // Also scale it by a percentage for fine tuning (default is 100%)    float currentVarianceFPS = (currentSmoothFPS - currentNowFPS);    currentVarianceFPS *= currentVarianceFPS;    currentVarianceFPS *= _pidCoefs.w;     // evaluate current error between the current smoothFPS and target FPS    // and the sqaure of the error to compare against the Variance    auto currentErrorFPS = (targetFPS - currentSmoothFPS);    auto currentErrorFPSSquare = currentErrorFPS * currentErrorFPS;     // Define a noiseCoef that is trying to adjust the error to the FPS target value based on its strength    // relative to the current Variance of the FPS signal.    // If the error is within the variance, just set to 0.    // if its within 2x the variance scale the control    // and full control if error is bigger than 2x variance    auto noiseCoef = 1.0f;    if (currentErrorFPSSquare < currentVarianceFPS) {        noiseCoef = 0.0f;    } else if (currentErrorFPSSquare < 2.0f * currentVarianceFPS) {        noiseCoef = (currentErrorFPSSquare - currentVarianceFPS) / currentVarianceFPS;    }     // The final normalized error is the the error to the FPS target, weighted by the noiseCoef, then normailzed by the target FPS.    // it s also clamped in the [-1, 1] range    auto error = noiseCoef * currentErrorFPS / targetFPS;    error = glm::clamp(error, -1.0f, 1.0f);     // Now we are getting into the P.I.D. controler code    // retreive the history of pid error and integral    auto previous_error = _pidHistory.x;    auto previous_integral = _pidHistory.y;     // The dt used for temporal values of the controller is the current realTimedelta    // clamped to a reasonable granularity to make sure we are not over reacting    auto dt = std::min(realTimeDelta, LOD_ADJUST_RUNNING_AVG_TIMESCALE);     // Compute the current integral and clamp to avoid accumulation    auto integral = previous_integral + error * dt;    glm::clamp(integral, -1.0f, 1.0f);     // Compute derivative    // dt is never zero because realTimeDelta would have early exit above, but if it ever was let's zero the derivative term    auto derivative = (dt <= 0.0f ? 0.0f : (error - previous_error) / dt);     // remember history    _pidHistory.x = error;    _pidHistory.y = integral;    _pidHistory.z = derivative;     // Compute the output of the PID and record intermediate results for tuning    _pidOutputs.x = _pidCoefs.x * error;        // Kp * error    _pidOutputs.y = _pidCoefs.y * integral;     // Ki * integral     _pidOutputs.z = _pidCoefs.z * derivative;   // Kd * derivative     auto output = _pidOutputs.x + _pidOutputs.y + _pidOutputs.z;    _pidOutputs.w = output;     // And now add the output of the controller to the LODAngle where we will guarantee it is in the proper range    setLODAngleDeg(oldLODAngle + output);     if (oldOctreeSizeScale != _octreeSizeScale) {        auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog();        if (lodToolsDialog) {            lodToolsDialog->reloadSliders();        }    }} float LODManager::getLODAngleHalfTan() const {    return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust);}float LODManager::getLODAngle() const {    return 2.0f * atanf(getLODAngleHalfTan());}float LODManager::getLODAngleDeg() const {    return glm::degrees(getLODAngle());} void LODManager::setLODAngleDeg(float lodAngle) {    auto newSolidAngle = std::max(0.5f, std::min(lodAngle, 90.f));    auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f));    auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan;    setOctreeSizeScale(octreeSizeScale);} void LODManager::setSmoothScale(float t) {    _smoothScale = glm::max(1.0f, t);} float LODManager::getPidKp() const {    return _pidCoefs.x;}float LODManager::getPidKi() const {    return _pidCoefs.y;}float LODManager::getPidKd() const {    return _pidCoefs.z;}float LODManager::getPidKv() const {    return _pidCoefs.w;}void LODManager::setPidKp(float k) {    _pidCoefs.x = k;}void LODManager::setPidKi(float k) {    _pidCoefs.y = k;}void LODManager::setPidKd(float k) {    _pidCoefs.z = k;}void LODManager::setPidKv(float t) {    _pidCoefs.w = t;} float LODManager::getPidOp() const {    return _pidOutputs.x;}float LODManager::getPidOi() const {    return _pidOutputs.y;}float LODManager::getPidOd() const {    return _pidOutputs.z;}float LODManager::getPidO() const {    return _pidOutputs.w;} void LODManager::resetLODAdjust() {} void LODManager::setAutomaticLODAdjust(bool value) {    _automaticLODAdjust = value;    emit autoLODChanged();} bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) {    // To decide if the bound should be rendered or not at the specified Args->lodAngle,    // we need to compute the apparent angle of the bound from the frustum origin,    // and compare it against the lodAngle, if it is greater or equal we should render the content of that bound.    // we abstract the bound as a sphere centered on the bound center and of radius half diagonal of the bound.     // Instead of comparing  angles, we are comparing the tangent of the half angle which are more efficient to compute:    // we are comparing the square of the half tangent apparent angle for the bound against the LODAngle Half tangent square    // if smaller, the bound is too small and we should NOT render it, return true otherwise.     // Tangent Adjacent side is eye to bound center vector length    auto pos = args->getViewFrustum().getPosition() - bounds.calcCenter();    auto halfTanAdjacentSq = glm::dot(pos, pos);     // Tangent Opposite side is the half length of the dimensions vector of the bound    auto dim = bounds.getDimensions();    auto halfTanOppositeSq = 0.25f * glm::dot(dim, dim);     // The test is:    // isVisible = halfTanSq >= lodHalfTanSq = (halfTanOppositeSq / halfTanAdjacentSq) >= lodHalfTanSq    // which we express as below to avoid division    // (halfTanOppositeSq) >= lodHalfTanSq * halfTanAdjacentSq    return (halfTanOppositeSq >= args->_lodAngleHalfTanSq * halfTanAdjacentSq);}; void LODManager::setOctreeSizeScale(float sizeScale) {    _octreeSizeScale = sizeScale;} void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {    _boundaryLevelAdjust = boundaryLevelAdjust;} QString LODManager::getLODFeedbackText() {    // determine granularity feedback    int boundaryLevelAdjust = getBoundaryLevelAdjust();    QString granularityFeedback;    switch (boundaryLevelAdjust) {    case 0: {        granularityFeedback = QString(".");    } break;    case 1: {        granularityFeedback = QString(" at half of standard granularity.");    } break;    case 2: {        granularityFeedback = QString(" at a third of standard granularity.");    } break;    default: {        granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1);    } break;    }    // distance feedback    float octreeSizeScale = getOctreeSizeScale();    float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE;    int relativeToTwentyTwenty = 20 / relativeToDefault;     QString result;    if (relativeToDefault > 1.01f) {        result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 2).arg(granularityFeedback);    } else if (relativeToDefault > 0.99f) {        result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback);    } else if (relativeToDefault > 0.01f) {        result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback);    } else {        result = QString("%2 of default distance for average vision%3").arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback);    }    return result;} void LODManager::loadSettings() {    setDesktopLODTargetFPS(desktopLODDecreaseFPS.get());    setHMDLODTargetFPS(hmdLODDecreaseFPS.get());} void LODManager::saveSettings() {    desktopLODDecreaseFPS.set(getDesktopLODTargetFPS());    hmdLODDecreaseFPS.set(getHMDLODTargetFPS());} const float MIN_DECREASE_FPS = 0.5f; void LODManager::setDesktopLODTargetFPS(float fps) {    if (fps < MIN_DECREASE_FPS) {        // avoid divide by zero        fps = MIN_DECREASE_FPS;    }    _desktopTargetFPS = fps;} float LODManager::getDesktopLODTargetFPS() const {    return _desktopTargetFPS;} void LODManager::setHMDLODTargetFPS(float fps) {    if (fps < MIN_DECREASE_FPS) {        // avoid divide by zero        fps = MIN_DECREASE_FPS;    }    _hmdTargetFPS = fps;} float LODManager::getHMDLODTargetFPS() const {    return _hmdTargetFPS;} float LODManager::getLODTargetFPS() const {    if (qApp->isHMDMode()) {        return getHMDLODTargetFPS();    }    return getDesktopLODTargetFPS();} void LODManager::setWorldDetailQuality(float quality) {    static const float MIN_FPS = 10;    static const float LOW = 0.25f;     bool isLowestValue = quality == LOW;    bool isHMDMode = qApp->isHMDMode();     float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS;    float desiredFPS = maxFPS;     if (!isLowestValue) {        float calculatedFPS = (maxFPS - (maxFPS * quality));        desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS;    }     if (isHMDMode) {        setHMDLODTargetFPS(desiredFPS);    } else {        setDesktopLODTargetFPS(desiredFPS);    }     emit worldDetailQualityChanged();} float LODManager::getWorldDetailQuality() const {     static const float LOW = 0.25f;    static const float MEDIUM = 0.5f;    static const float HIGH = 0.75f;     bool inHMD = qApp->isHMDMode();     float targetFPS = 0.0f;    if (inHMD) {        targetFPS = getHMDLODTargetFPS();    } else {        targetFPS = getDesktopLODTargetFPS();    }    float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS;    float percentage = 1.0f - targetFPS / maxFPS;     if (percentage <= LOW) {        return LOW;    } else if (percentage <= MEDIUM) {        return MEDIUM;    }     return HIGH;}  void LODManager::setLODQualityLevel(float quality) {    _lodQualityLevel = quality;} float LODManager::getLODQualityLevel() const {    return _lodQualityLevel;}