Merge pull request #12022 from AndrewMeadows/lod-auto-adjust-002

more responsive LOD auto-adjust
This commit is contained in:
Brad Hefta-Gaub 2017-12-20 19:23:39 -08:00 committed by GitHub
commit 15fcf66d0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 167 deletions

View file

@ -4323,9 +4323,11 @@ void Application::updateLOD(float deltaTime) const {
PerformanceTimer perfTimer("LOD");
// adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
if (!isThrottleRendering()) {
float batchTime = (float)_gpuContext->getFrameTimerBatchAverage();
float presentTime = getActiveDisplayPlugin()->getAveragePresentTime();
float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime());
DependencyManager::get<LODManager>()->autoAdjustLOD(batchTime, engineRunTime, deltaTime);
float gpuTime = getGPUContext()->getFrameTimerGPUAverage();
float maxRenderTime = glm::max(gpuTime, glm::max(presentTime, engineRunTime));
DependencyManager::get<LODManager>()->autoAdjustLOD(maxRenderTime, deltaTime);
} else {
DependencyManager::get<LODManager>()->resetLODAdjust();
}

View file

@ -19,6 +19,7 @@
#include "LODManager.h"
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS);
@ -39,156 +40,95 @@ float LODManager::getLODIncreaseFPS() {
return getDesktopLODIncreaseFPS();
}
void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec) {
// We use a "time-weighted running average" of the renderTime 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.1f; // sec
//
// Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its
// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage settle
// to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few
// multiples of the running average timescale:
const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec
// NOTE: our first ~100 samples at app startup are completely all over the place, and we don't
// really want to count them in our average, so we will ignore the real frame rates and stuff
// our moving average with simulated good data
const int IGNORE_THESE_SAMPLES = 100;
if (_fpsAverageUpWindow.getSampleCount() < IGNORE_THESE_SAMPLES) {
_lastStable = _lastUpShift = _lastDownShift = usecTimestampNow();
}
const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f;
const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f;
void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) {
// compute time-weighted running average renderTime
const float OVERLAY_AND_SWAP_TIME_BUDGET = 2.0f; // msec
float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET;
float maxTime = glm::max(renderTime, engineRunTime);
const float BLEND_TIMESCALE = 0.3f; // sec
const float MIN_DELTA_TIME = 0.001f;
const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME);
float blend = BLEND_TIMESCALE / safeDeltaTime;
if (blend > 1.0f) {
blend = 1.0f;
// Note: we MUST clamp the blend to 1.0 for stability
float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f;
_avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * renderTime; // msec
if (!_automaticLODAdjust) {
// early exit
return;
}
_avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxTime; // msec
// translate into fps for legacy implementation
float oldOctreeSizeScale = _octreeSizeScale;
float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime;
_fpsAverageStartWindow.updateAverage(currentFPS);
_fpsAverageDownWindow.updateAverage(currentFPS);
_fpsAverageUpWindow.updateAverage(currentFPS);
quint64 now = usecTimestampNow();
quint64 elapsedSinceDownShift = now - _lastDownShift;
quint64 elapsedSinceUpShift = now - _lastUpShift;
quint64 lastStableOrUpshift = glm::max(_lastUpShift, _lastStable);
quint64 elapsedSinceStableOrUpShift = now - lastStableOrUpshift;
if (_automaticLODAdjust) {
bool changed = false;
// LOD Downward adjustment
// If we've been downshifting, we watch a shorter downshift window so that we will quickly move toward our
// target frame rate. But if we haven't just done a downshift (either because our last shift was an upshift,
// or because we've just started out) then we look at a much longer window to consider whether or not to start
// downshifting.
bool doDownShift = false;
if (_isDownshifting) {
// only consider things if our DOWN_SHIFT time has elapsed...
if (elapsedSinceDownShift > DOWN_SHIFT_ELPASED) {
doDownShift = _fpsAverageDownWindow.getAverage() < getLODDecreaseFPS();
if (!doDownShift) {
qCDebug(interfaceapp) << "---- WE APPEAR TO BE DONE DOWN SHIFTING -----";
_isDownshifting = false;
_lastStable = now;
}
}
} else {
doDownShift = (elapsedSinceStableOrUpShift > START_SHIFT_ELPASED
&& _fpsAverageStartWindow.getAverage() < getLODDecreaseFPS());
}
if (doDownShift) {
// Octree items... stepwise adjustment
uint64_t now = usecTimestampNow();
if (currentFPS < getLODDecreaseFPS()) {
if (now > _decreaseFPSExpiry) {
_decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale *= ADJUST_LOD_DOWN_BY;
_octreeSizeScale *= LOD_AUTO_ADJUST_DECREMENT_FACTOR;
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
}
changed = true;
}
if (changed) {
if (_isDownshifting) {
// subsequent downshift
qCDebug(interfaceapp) << "adjusting LOD DOWN..."
<< "average fps for last "<< DOWN_SHIFT_WINDOW_IN_SECS <<"seconds was "
<< _fpsAverageDownWindow.getAverage()
<< "minimum is:" << getLODDecreaseFPS()
<< "elapsedSinceDownShift:" << elapsedSinceDownShift
<< " NEW _octreeSizeScale=" << _octreeSizeScale;
} else {
// first downshift
qCDebug(interfaceapp) << "adjusting LOD DOWN after initial delay..."
<< "average fps for last "<< START_DELAY_WINDOW_IN_SECS <<"seconds was "
<< _fpsAverageStartWindow.getAverage()
<< "minimum is:" << getLODDecreaseFPS()
<< "elapsedSinceUpShift:" << elapsedSinceUpShift
<< " NEW _octreeSizeScale=" << _octreeSizeScale;
}
_lastDownShift = now;
_isDownshifting = true;
qCDebug(interfaceapp) << "adjusting LOD DOWN"
<< "fps =" << currentFPS
<< "targetFPS =" << getLODDecreaseFPS()
<< "octreeSizeScale =" << _octreeSizeScale;
emit LODDecreased();
}
} else {
// LOD Upward adjustment
if (elapsedSinceUpShift > UP_SHIFT_ELPASED) {
if (_fpsAverageUpWindow.getAverage() > getLODIncreaseFPS()) {
// Octee items... stepwise adjustment
if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
} else {
_octreeSizeScale *= ADJUST_LOD_UP_BY;
}
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
}
changed = true;
}
}
if (changed) {
qCDebug(interfaceapp) << "adjusting LOD UP... average fps for last "<< UP_SHIFT_WINDOW_IN_SECS <<"seconds was "
<< _fpsAverageUpWindow.getAverage()
<< "upshift point is:" << getLODIncreaseFPS()
<< "elapsedSinceUpShift:" << elapsedSinceUpShift
<< " NEW _octreeSizeScale=" << _octreeSizeScale;
_lastUpShift = now;
_isDownshifting = false;
emit LODIncreased();
}
}
_decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
}
if (changed) {
auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog();
if (lodToolsDialog) {
lodToolsDialog->reloadSliders();
_increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
} else if (currentFPS > getLODIncreaseFPS()) {
if (now > _increaseFPSExpiry) {
_increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
} else {
_octreeSizeScale *= LOD_AUTO_ADJUST_INCREMENT_FACTOR;
}
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
}
qCDebug(interfaceapp) << "adjusting LOD UP"
<< "fps =" << currentFPS
<< "targetFPS =" << getLODDecreaseFPS()
<< "octreeSizeScale =" << _octreeSizeScale;
emit LODIncreased();
}
_increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
}
_decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
} else {
_increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD;
_decreaseFPSExpiry = _increaseFPSExpiry;
}
if (oldOctreeSizeScale != _octreeSizeScale) {
auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog();
if (lodToolsDialog) {
lodToolsDialog->reloadSliders();
}
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime
// to be at middle of target zone. It will drift close to its true value within
// about three few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary.
float expectedFPS = 0.5f * (getLODIncreaseFPS() + getLODDecreaseFPS());
_avgRenderTime = MSECS_PER_SECOND / expectedFPS;
}
}
void LODManager::resetLODAdjust() {
_fpsAverageStartWindow.reset();
_fpsAverageDownWindow.reset();
_fpsAverageUpWindow.reset();
_lastUpShift = _lastDownShift = usecTimestampNow();
_isDownshifting = false;
_decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD;
}
const float MIN_DECREASE_FPS = 0.5f;
@ -206,7 +146,7 @@ float LODManager::getDesktopLODDecreaseFPS() const {
}
float LODManager::getDesktopLODIncreaseFPS() const {
return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_DESKTOP_FPS);
return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS);
}
void LODManager::setHMDLODDecreaseFPS(float fps) {
@ -222,7 +162,7 @@ float LODManager::getHMDLODDecreaseFPS() const {
}
float LODManager::getHMDLODIncreaseFPS() const {
return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_HMD_FPS);
return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS);
}
QString LODManager::getLODFeedbackText() {

View file

@ -19,29 +19,13 @@
#include <SimpleMovingAverage.h>
#include <render/Args.h>
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 20.0;
const float DEFAULT_HMD_LOD_DOWN_FPS = 20.0;
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f;
const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f;
const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec
const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec
const float MAX_LIKELY_DESKTOP_FPS = 59.0; // this is essentially, V-synch - 1 fps
const float MAX_LIKELY_HMD_FPS = 74.0; // this is essentially, V-synch - 1 fps
const float INCREASE_LOD_GAP = 15.0f;
const float START_DELAY_WINDOW_IN_SECS = 3.0f; // wait at least this long after steady state/last upshift to consider downshifts
const float DOWN_SHIFT_WINDOW_IN_SECS = 0.5f;
const float UP_SHIFT_WINDOW_IN_SECS = 2.5f;
const int ASSUMED_FPS = 60;
const quint64 START_SHIFT_ELPASED = USECS_PER_SECOND * START_DELAY_WINDOW_IN_SECS;
const quint64 DOWN_SHIFT_ELPASED = USECS_PER_SECOND * DOWN_SHIFT_WINDOW_IN_SECS; // Consider adjusting LOD down after half a second
const quint64 UP_SHIFT_ELPASED = USECS_PER_SECOND * UP_SHIFT_WINDOW_IN_SECS;
const int START_DELAY_SAMPLES_OF_FRAMES = ASSUMED_FPS * START_DELAY_WINDOW_IN_SECS;
const int DOWN_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * DOWN_SHIFT_WINDOW_IN_SECS;
const int UP_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * UP_SHIFT_WINDOW_IN_SECS;
const float ADJUST_LOD_DOWN_BY = 0.9f;
const float ADJUST_LOD_UP_BY = 1.1f;
const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps
const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps
const float INCREASE_LOD_GAP_FPS = 15.0f; // fps
// The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision).
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
@ -78,7 +62,7 @@ public:
Q_INVOKABLE float getLODIncreaseFPS();
static bool shouldRender(const RenderArgs* args, const AABox& bounds);
void autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec);
void autoAdjustLOD(float renderTime, float realTimeDelta);
void loadSettings();
void saveSettings();
@ -92,21 +76,15 @@ private:
LODManager();
bool _automaticLODAdjust = true;
float _avgRenderTime { 0.0 };
float _avgRenderTime { 0.0f };
float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME };
float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME };
float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
int _boundaryLevelAdjust = 0;
quint64 _lastDownShift = 0;
quint64 _lastUpShift = 0;
quint64 _lastStable = 0;
bool _isDownshifting = false; // start out as if we're not downshifting
SimpleMovingAverage _fpsAverageStartWindow = START_DELAY_SAMPLES_OF_FRAMES;
SimpleMovingAverage _fpsAverageDownWindow = DOWN_SHIFT_SAMPLES_OF_FRAMES;
SimpleMovingAverage _fpsAverageUpWindow = UP_SHIFT_SAMPLES_OF_FRAMES;
uint64_t _decreaseFPSExpiry { 0 };
uint64_t _increaseFPSExpiry { 0 };
};
#endif // hifi_LODManager_h

View file

@ -679,6 +679,7 @@ void OpenGLDisplayPlugin::internalPresent() {
void OpenGLDisplayPlugin::present() {
auto frameId = (uint64_t)presentCount();
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId)
uint64_t startPresent = usecTimestampNow();
{
PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId)
updateFrameData();
@ -713,6 +714,7 @@ void OpenGLDisplayPlugin::present() {
gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory());
}
_movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent));
}
float OpenGLDisplayPlugin::newFramePresentRate() const {

View file

@ -21,8 +21,10 @@
#include <QtCore/QWaitCondition>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <RegisteredMetaTypes.h>
#include <shared/Bilateral.h>
#include <SimpleMovingAverage.h>
#include <gpu/Forward.h>
#include "Plugin.h"
@ -203,6 +205,7 @@ public:
virtual void cycleDebugOutput() {}
void waitForPresent();
float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> getHUDOperator();
@ -220,6 +223,8 @@ protected:
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator { std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)>() };
MovingAverage<float, 10> _movingAveragePresent;
private:
QMutex _presentMutex;
QWaitCondition _presentCondition;