Merge pull request #2002 from ZappoMan/lowerLODnoFPSdrop

Automatically Adjust LOD
This commit is contained in:
Philip Rosedale 2014-02-16 18:14:24 -08:00
commit 37e1c00828
9 changed files with 219 additions and 41 deletions

View file

@ -12,7 +12,7 @@
uniform sampler2D permutationNormalTexture;
// the noise frequency
const float frequency = 1024.0;
const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
// the noise amplitude
const float amplitude = 0.1;

View file

@ -2740,7 +2740,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxels...");
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
_voxels.render();
// double check that our LOD doesn't need to be auto-adjusted
// only adjust if our option is set
if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) {
Menu::getInstance()->autoAdjustLOD(_fps);
}
}
}
@ -2831,7 +2837,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_mouseVoxel.s,
_mouseVoxel.s);
_sharedVoxelSystem.render(true);
_sharedVoxelSystem.render();
glPopMatrix();
}
}

View file

@ -70,7 +70,8 @@ Menu::Menu() :
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_boundaryLevelAdjust(0),
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS)
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS),
_lastAdjust(usecTimestampNow())
{
Application *appInstance = Application::getInstance();
@ -318,6 +319,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutoAdjustLOD);
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
@ -1117,14 +1119,38 @@ void Menu::voxelStatsDetailsClosed() {
}
}
void Menu::autoAdjustLOD(float currentFPS) {
bool changed = false;
quint64 now = usecTimestampNow();
quint64 elapsed = now - _lastAdjust;
if (elapsed > ADJUST_LOD_DOWN_DELAY && currentFPS < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
_voxelSizeScale *= ADJUST_LOD_DOWN_BY;
changed = true;
_lastAdjust = now;
qDebug() << "adjusting LOD down... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
}
if (elapsed > ADJUST_LOD_UP_DELAY && currentFPS > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
_voxelSizeScale *= ADJUST_LOD_UP_BY;
changed = true;
_lastAdjust = now;
qDebug() << "adjusting LOD up... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
}
if (changed) {
if (_lodToolsDialog) {
_lodToolsDialog->reloadSliders();
}
}
}
void Menu::setVoxelSizeScale(float sizeScale) {
_voxelSizeScale = sizeScale;
Application::getInstance()->getVoxels()->redrawInViewVoxels();
}
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust;
Application::getInstance()->getVoxels()->redrawInViewVoxels();
}
void Menu::lodTools() {

View file

@ -16,6 +16,18 @@
#include <AbstractMenuInterface.h>
const float ADJUST_LOD_DOWN_FPS = 40.0;
const float ADJUST_LOD_UP_FPS = 55.0;
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
const float ADJUST_LOD_DOWN_BY = 0.9f;
const float ADJUST_LOD_UP_BY = 1.1f;
const float ADJUST_LOD_MIN_SIZE_SCALE = TREE_SCALE * 1.0f;
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
enum FrustumDrawMode {
FRUSTUM_DRAW_MODE_ALL,
FRUSTUM_DRAW_MODE_VECTORS,
@ -68,6 +80,7 @@ public:
void handleViewFrustumOffsetKeyModifier(int key);
// User Tweakable LOD Items
void autoAdjustLOD(float currentFPS);
void setVoxelSizeScale(float sizeScale);
float getVoxelSizeScale() const { return _voxelSizeScale; }
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
@ -157,6 +170,7 @@ private:
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
quint64 _lastAdjust;
};
namespace MenuOption {
@ -164,6 +178,7 @@ namespace MenuOption {
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Avatars = "Avatars";
const QString Atmosphere = "Atmosphere";
const QString AutoAdjustLOD = "Automatically Adjust LOD";
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";

View file

@ -57,9 +57,12 @@ GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 };
VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
: NodeData(),
_treeScale(treeScale),
_maxVoxels(maxVoxels),
_initialized(false) {
_treeScale(treeScale),
_maxVoxels(maxVoxels),
_initialized(false),
_writeArraysLock(QReadWriteLock::Recursive),
_readArraysLock(QReadWriteLock::Recursive)
{
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
_writeRenderFullVBO = true;
@ -99,6 +102,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_culledOnce = false;
_inhideOutOfView = false;
_lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
_lastKnownBoundaryLevelAdjust = 0;
}
void VoxelSystem::elementDeleted(OctreeElement* element) {
@ -121,6 +127,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) {
void VoxelSystem::elementUpdated(OctreeElement* element) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
return;
@ -249,6 +256,9 @@ VoxelSystem::~VoxelSystem() {
delete _tree;
}
// This is called by the main application thread on both the initialization of the application and when
// the preferences dialog box is called/saved
void VoxelSystem::setMaxVoxels(int maxVoxels) {
if (maxVoxels == _maxVoxels) {
return;
@ -267,6 +277,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) {
}
}
// This is called by the main application thread on both the initialization of the application and when
// the use voxel shader menu item is chosen
void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
if (_useVoxelShader == useVoxelShader) {
return;
@ -330,7 +342,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) {
void VoxelSystem::cleanupVoxelMemory() {
if (_initialized) {
_bufferWriteLock.lock();
_readArraysLock.lockForWrite();
_initialized = false; // no longer initialized
if (_useVoxelShader) {
// these are used when in VoxelShader mode.
@ -368,7 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
delete[] _writeVoxelDirtyArray;
delete[] _readVoxelDirtyArray;
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
_bufferWriteLock.unlock();
_readArraysLock.unlock();
}
}
@ -401,7 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice
}
void VoxelSystem::initVoxelMemory() {
_bufferWriteLock.lock();
_readArraysLock.lockForWrite();
_writeArraysLock.lockForWrite();
_memoryUsageRAM = 0;
_memoryUsageVBO = 0; // our VBO allocations as we know them
@ -516,7 +529,8 @@ void VoxelSystem::initVoxelMemory() {
_initialized = true;
_bufferWriteLock.unlock();
_writeArraysLock.unlock();
_readArraysLock.unlock();
}
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
@ -646,7 +660,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
_inSetupNewVoxelsForDrawing = true;
bool didWriteFullVBO = _writeRenderFullVBO;
if (_tree->isDirty()) {
static char buffer[64] = { 0 };
@ -673,7 +687,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
_bufferWriteLock.lock();
_readArraysLock.lockForWrite();
if (_voxelsUpdated) {
_voxelsDirty=true;
@ -682,7 +696,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(didWriteFullVBO);
_bufferWriteLock.unlock();
_readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@ -713,8 +727,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
_bufferWriteLock.lock();
"setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" );
_readArraysLock.lockForWrite();
}
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
@ -725,7 +739,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// after...
_voxelsUpdated = 0;
_bufferWriteLock.unlock();
_readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@ -733,8 +747,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
}
void VoxelSystem::checkForCulling() {
class recreateVoxelGeometryInViewArgs {
public:
VoxelSystem* thisVoxelSystem;
ViewFrustum thisViewFrustum;
unsigned long nodesScanned;
float voxelSizeScale;
int boundaryLevelAdjust;
recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) :
thisVoxelSystem(voxelSystem),
thisViewFrustum(*voxelSystem->getViewFrustum()),
nodesScanned(0),
voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()),
boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust())
{
}
};
// The goal of this operation is to remove any old references to old geometry, and if the voxel
// should be visible, create new geometry for it.
bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData;
args->nodesScanned++;
// reset the old geometry...
// note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the
// old geometry
voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust);
bool inView = voxel->isInView(args->thisViewFrustum);
voxel->setShouldRender(inView && shouldRender);
if (shouldRender && inView) {
// recreate the geometry
args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW
}
return true; // keep recursing!
}
// TODO: does cleanupRemovedVoxels() ever get called?
// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots???
void VoxelSystem::recreateVoxelGeometryInView() {
qDebug() << "recreateVoxelGeometryInView()...";
recreateVoxelGeometryInViewArgs args(this);
_writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done
_tree->lockForRead(); // don't let anyone change our tree structure until we're run
// reset our write arrays bookkeeping to think we've got no voxels in it
clearFreeBufferIndexes();
// do we need to reset out _writeVoxelDirtyArray arrays??
memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
_tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args);
_tree->unlock();
_writeArraysLock.unlock();
}
void VoxelSystem::checkForCulling() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
quint64 start = usecTimestampNow();
@ -762,7 +841,20 @@ void VoxelSystem::checkForCulling() {
_hasRecentlyChanged = false;
}
hideOutOfView(forceFullFrustum);
// This would be a good place to do a special processing pass, for example, switching the LOD of the scene
bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() ||
_lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust());
// track that these values
_lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
_lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
if (fullRedraw) {
// this will remove all old geometry and recreate the correct geometry for all in view voxels
recreateVoxelGeometryInView();
} else {
hideOutOfView(forceFullFrustum);
}
if (forceFullFrustum) {
quint64 endViewCulling = usecTimestampNow();
@ -880,12 +972,26 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"copyWrittenDataToReadArrays()");
if (_voxelsDirty && _voxelsUpdated) {
if (fullVBOs) {
copyWrittenDataToReadArraysFullVBOs();
// attempt to get the writeArraysLock for reading and the readArraysLock for writing
// so we can copy from the write to the read... if we fail, that's ok, we'll get it the next
// time around, the only side effect is the VBOs won't be updated this frame
const int WAIT_FOR_LOCK_IN_MS = 5;
if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) {
if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
if (_voxelsDirty && _voxelsUpdated) {
if (fullVBOs) {
copyWrittenDataToReadArraysFullVBOs();
} else {
copyWrittenDataToReadArraysPartialVBOs();
}
}
_writeArraysLock.unlock();
} else {
copyWrittenDataToReadArraysPartialVBOs();
qDebug() << "couldn't get _writeArraysLock.LockForRead()...";
}
_readArraysLock.unlock();
} else {
qDebug() << "couldn't get _readArraysLock.LockForWrite()...";
}
}
@ -1141,17 +1247,27 @@ void VoxelSystem::updateVBOs() {
// would like to include _callsToTreesToArrays
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
if (_voxelsDirty) {
if (_readRenderFullVBO) {
updateFullVBOs();
// attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
// if we fail to get the lock, that's ok, our VBOs will update on the next frame...
const int WAIT_FOR_LOCK_IN_MS = 5;
if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
if (_readRenderFullVBO) {
updateFullVBOs();
} else {
updatePartialVBOs();
}
_voxelsDirty = false;
_readRenderFullVBO = false;
_readArraysLock.unlock();
} else {
updatePartialVBOs();
qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
}
_voxelsDirty = false;
_readRenderFullVBO = false;
}
_callsToTreesToArrays = 0; // clear it
}
// this should only be called on the main application thread during render
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarning, "updateVBOSegment()");
@ -1197,7 +1313,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg
}
}
void VoxelSystem::render(bool texture) {
void VoxelSystem::render() {
bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures);
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "render()");
@ -1404,11 +1521,7 @@ void VoxelSystem::killLocalVoxels() {
setupNewVoxelsForDrawing();
}
void VoxelSystem::redrawInViewVoxels() {
hideOutOfView(true);
}
// only called on main thread
bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) {
_nodeCount++;
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
@ -1416,12 +1529,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void
return true;
}
// only called on main thread, and also always followed by a call to cleanupVoxelMemory()
// you shouldn't be calling this on any other thread or without also cleaning up voxel memory
void VoxelSystem::clearAllNodesBufferIndex() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::clearAllNodesBufferIndex()");
_nodeCount = 0;
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
clearFreeBufferIndexes(); // this should be called too
_tree->unlock();
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug("clearing buffer index of %d nodes", _nodeCount);

View file

@ -53,7 +53,7 @@ public:
virtual void init();
void simulate(float deltaTime) { }
void render(bool texture);
void render();
void changeTree(VoxelTree* newTree);
VoxelTree* getTree() const { return _tree; }
@ -79,7 +79,6 @@ public:
unsigned long getVoxelMemoryUsageGPU();
void killLocalVoxels();
void redrawInViewVoxels();
virtual void removeOutOfView();
virtual void hideOutOfView(bool forceFullFrustum = false);
@ -151,6 +150,7 @@ protected:
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
void checkForCulling();
void recreateVoxelGeometryInView();
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
@ -194,6 +194,7 @@ private:
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData);
static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData);
static bool recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData);
int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw);
int forceRemoveNodeFromArrays(VoxelTreeElement* node);
@ -211,6 +212,11 @@ private:
GLfloat* _readVerticesArray;
GLubyte* _readColorsArray;
QReadWriteLock _writeArraysLock;
QReadWriteLock _readArraysLock;
GLfloat* _writeVerticesArray;
GLubyte* _writeColorsArray;
bool* _writeVoxelDirtyArray;
@ -253,9 +259,6 @@ private:
GLuint _vboIndicesFront;
GLuint _vboIndicesBack;
QMutex _bufferWriteLock;
QMutex _treeLock;
ViewFrustum _lastKnownViewFrustum;
ViewFrustum _lastStableViewFrustum;
ViewFrustum* _viewFrustum;
@ -299,6 +302,9 @@ private:
bool _useFastVoxelPipeline;
bool _inhideOutOfView;
float _lastKnownVoxelSizeScale;
int _lastKnownBoundaryLevelAdjust;
};
#endif

View file

@ -121,6 +121,12 @@ LodToolsDialog::~LodToolsDialog() {
delete _boundaryLevelAdjust;
}
void LodToolsDialog::reloadSliders() {
_lodSize->setValue(Menu::getInstance()->getVoxelSizeScale() / TREE_SCALE);
_boundaryLevelAdjust->setValue(Menu::getInstance()->getBoundaryLevelAdjust());
_feedback->setText(getFeedbackText());
}
void LodToolsDialog::sizeScaleValueChanged(int value) {
float realValue = value * TREE_SCALE;
Menu::getInstance()->setVoxelSizeScale(realValue);

View file

@ -28,6 +28,7 @@ public slots:
void sizeScaleValueChanged(int value);
void boundaryLevelValueChanged(int value);
void resetClicked(bool checked);
void reloadSliders();
protected:

View file

@ -45,6 +45,8 @@ public:
_alwaysDisplay(alwaysDisplay),
_runningTotal(runningTotal),
_totalCalls(totalCalls) { }
quint64 elapsed() const { return (usecTimestampNow() - _start); };
~PerformanceWarning();