Showing the changes to interface/src/VoxelSystem.cpp (1 of 80 files). Show all
Unified · Split
| @@ -57,13 +57,16 @@ GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 }; · 56 unmodified lines | ||
| 57 | 57 | |
| 58 | 58 | VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) |
| 59 | 59 | : NodeData(), |
| 60 | − _treeScale(treeScale), | |
| 61 | − _maxVoxels(maxVoxels), | |
| 62 | − _initialized(false), | |
| 63 | − _inOcclusions(false), | |
| 64 | − _showCulledSharedFaces(false), | |
| 65 | − _usePrimitiveRenderer(false), | |
| 66 | − _renderer(0) { | |
| 60 | + _treeScale(treeScale), | |
| 61 | + _maxVoxels(maxVoxels), | |
| 62 | + _initialized(false), | |
| 63 | + _writeArraysLock(QReadWriteLock::Recursive), | |
| 64 | + _readArraysLock(QReadWriteLock::Recursive), | |
| 65 | + _inOcclusions(false), | |
| 66 | + _showCulledSharedFaces(false), | |
| 67 | + _usePrimitiveRenderer(false), | |
| 68 | + _renderer(0) | |
| 69 | +{ | |
| 67 | 70 | |
| 68 | 71 | _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; |
| 69 | 72 | _writeRenderFullVBO = true; |
| @@ -103,6 +106,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) · 33 unmodified lines | ||
| 103 | 106 | |
| 104 | 107 | _culledOnce = false; |
| 105 | 108 | _inhideOutOfView = false; |
| 109 | + | |
| 110 | + _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE; | |
| 111 | + _lastKnownBoundaryLevelAdjust = 0; | |
| 106 | 112 | } |
| 107 | 113 | |
| 108 | 114 | void VoxelSystem::elementDeleted(OctreeElement* element) { |
| @@ -125,6 +131,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) { · 16 unmodified lines | ||
| 125 | 131 | |
| 126 | 132 | void VoxelSystem::elementUpdated(OctreeElement* element) { |
| 127 | 133 | VoxelTreeElement* voxel = (VoxelTreeElement*)element; |
| 134 | + | |
| 128 | 135 | // If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail.. |
| 129 | 136 | if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) { |
| 130 | 137 | return; |
| @@ -253,6 +260,9 @@ VoxelSystem::~VoxelSystem() { · 122 unmodified lines | ||
| 253 | 260 | delete _tree; |
| 254 | 261 | } |
| 255 | 262 | |
| 263 | + | |
| 264 | +// This is called by the main application thread on both the initialization of the application and when | |
| 265 | +// the preferences dialog box is called/saved | |
| 256 | 266 | void VoxelSystem::setMaxVoxels(int maxVoxels) { |
| 257 | 267 | if (maxVoxels == _maxVoxels) { |
| 258 | 268 | return; |
| @@ -271,6 +281,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) { · 12 unmodified lines | ||
| 271 | 281 | } |
| 272 | 282 | } |
| 273 | 283 | |
| 284 | +// This is called by the main application thread on both the initialization of the application and when | |
| 285 | +// the use voxel shader menu item is chosen | |
| 274 | 286 | void VoxelSystem::setUseVoxelShader(bool useVoxelShader) { |
| 275 | 287 | if (_useVoxelShader == useVoxelShader) { |
| 276 | 288 | return; |
| @@ -335,7 +347,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) { · 58 unmodified lines | ||
| 335 | 347 | |
| 336 | 348 | void VoxelSystem::cleanupVoxelMemory() { |
| 337 | 349 | if (_initialized) { |
| 338 | − _bufferWriteLock.lock(); | |
| 350 | + _readArraysLock.lockForWrite(); | |
| 339 | 351 | _initialized = false; // no longer initialized |
| 340 | 352 | if (_useVoxelShader) { |
| 341 | 353 | // these are used when in VoxelShader mode. |
| @@ -376,7 +388,7 @@ void VoxelSystem::cleanupVoxelMemory() { · 34 unmodified lines | ||
| 376 | 388 | delete[] _writeVoxelDirtyArray; |
| 377 | 389 | delete[] _readVoxelDirtyArray; |
| 378 | 390 | _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; |
| 379 | − _bufferWriteLock.unlock(); | |
| 391 | + _readArraysLock.unlock(); | |
| 380 | 392 | } |
| 381 | 393 | } |
| 382 | 394 | |
| @@ -409,7 +421,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice · 26 unmodified lines | ||
| 409 | 421 | } |
| 410 | 422 | |
| 411 | 423 | void VoxelSystem::initVoxelMemory() { |
| 412 | − _bufferWriteLock.lock(); | |
| 424 | + _readArraysLock.lockForWrite(); | |
| 425 | + _writeArraysLock.lockForWrite(); | |
| 413 | 426 | |
| 414 | 427 | _memoryUsageRAM = 0; |
| 415 | 428 | _memoryUsageVBO = 0; // our VBO allocations as we know them |
| @@ -524,7 +537,8 @@ void VoxelSystem::initVoxelMemory() { · 108 unmodified lines | ||
| 524 | 537 | |
| 525 | 538 | _initialized = true; |
| 526 | 539 | |
| 527 | − _bufferWriteLock.unlock(); | |
| 540 | + _writeArraysLock.unlock(); | |
| 541 | + _readArraysLock.unlock(); | |
| 528 | 542 | } |
| 529 | 543 | |
| 530 | 544 | void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const { |
| @@ -654,7 +668,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() { · 123 unmodified lines | ||
| 654 | 668 | } |
| 655 | 669 | |
| 656 | 670 | _inSetupNewVoxelsForDrawing = true; |
| 657 | − | |
| 671 | + | |
| 658 | 672 | bool didWriteFullVBO = _writeRenderFullVBO; |
| 659 | 673 | if (_tree->isDirty()) { |
| 660 | 674 | static char buffer[64] = { 0 }; |
| @@ -688,10 +702,9 @@ void VoxelSystem::setupNewVoxelsForDrawing() { · 27 unmodified lines | ||
| 688 | 702 | if (_voxelsUpdated) { |
| 689 | 703 | _voxelsDirty=true; |
| 690 | 704 | } |
| 691 | − } | |
| 692 | − else { | |
| 705 | + } else { | |
| 693 | 706 | // lock on the buffer write lock so we can't modify the data when the GPU is reading it |
| 694 | − _bufferWriteLock.lock(); | |
| 707 | + _readArraysLock.lockForWrite(); | |
| 695 | 708 | |
| 696 | 709 | if (_voxelsUpdated) { |
| 697 | 710 | _voxelsDirty=true; |
| @@ -699,8 +712,8 @@ void VoxelSystem::setupNewVoxelsForDrawing() { · 1 unmodified line | ||
| 699 | 712 | |
| 700 | 713 | // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated |
| 701 | 714 | copyWrittenDataToReadArrays(didWriteFullVBO); |
| 715 | + _readArraysLock.unlock(); | |
| 702 | 716 | |
| 703 | − _bufferWriteLock.unlock(); | |
| 704 | 717 | } |
| 705 | 718 | |
| 706 | 719 | quint64 end = usecTimestampNow(); |
| @@ -732,13 +745,12 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { · 25 unmodified lines | ||
| 732 | 745 | if (_usePrimitiveRenderer) { |
| 733 | 746 | _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty |
| 734 | 747 | _voxelsUpdated = 0; |
| 735 | − } | |
| 736 | − else { | |
| 748 | + } else { | |
| 737 | 749 | // lock on the buffer write lock so we can't modify the data when the GPU is reading it |
| 738 | 750 | { |
| 739 | 751 | PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), |
| 740 | 752 | "setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" ); |
| 741 | − _bufferWriteLock.lock(); | |
| 753 | + _readArraysLock.lockForWrite(); | |
| 742 | 754 | } |
| 743 | 755 | |
| 744 | 756 | _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty |
| @@ -748,8 +760,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { · 3 unmodified lines | ||
| 748 | 760 | |
| 749 | 761 | // after... |
| 750 | 762 | _voxelsUpdated = 0; |
| 751 | − | |
| 752 | − _bufferWriteLock.unlock(); | |
| 763 | + _readArraysLock.unlock(); | |
| 753 | 764 | } |
| 754 | 765 | quint64 end = usecTimestampNow(); |
| 755 | 766 | int elapsedmsec = (end - start) / 1000; |
| @@ -757,8 +768,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { · 1 unmodified line | ||
| 757 | 768 | _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; |
| 758 | 769 | } |
| 759 | 770 | |
| 760 | −void VoxelSystem::checkForCulling() { | |
| 761 | 771 | |
| 772 | + | |
| 773 | +class recreateVoxelGeometryInViewArgs { | |
| 774 | +public: | |
| 775 | + VoxelSystem* thisVoxelSystem; | |
| 776 | + ViewFrustum thisViewFrustum; | |
| 777 | + unsigned long nodesScanned; | |
| 778 | + float voxelSizeScale; | |
| 779 | + int boundaryLevelAdjust; | |
| 780 | + | |
| 781 | + recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) : | |
| 782 | + thisVoxelSystem(voxelSystem), | |
| 783 | + thisViewFrustum(*voxelSystem->getViewFrustum()), | |
| 784 | + nodesScanned(0), | |
| 785 | + voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()), | |
| 786 | + boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust()) | |
| 787 | + { | |
| 788 | + } | |
| 789 | +}; | |
| 790 | + | |
| 791 | +// The goal of this operation is to remove any old references to old geometry, and if the voxel | |
| 792 | +// should be visible, create new geometry for it. | |
| 793 | +bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) { | |
| 794 | + VoxelTreeElement* voxel = (VoxelTreeElement*)element; | |
| 795 | + recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData; | |
| 796 | + | |
| 797 | + args->nodesScanned++; | |
| 798 | + | |
| 799 | + // reset the old geometry... | |
| 800 | + // note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the | |
| 801 | + // old geometry | |
| 802 | + voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); | |
| 803 | + | |
| 804 | + bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust); | |
| 805 | + bool inView = voxel->isInView(args->thisViewFrustum); | |
| 806 | + voxel->setShouldRender(inView && shouldRender); | |
| 807 | + if (shouldRender && inView) { | |
| 808 | + // recreate the geometry | |
| 809 | + args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW | |
| 810 | + } | |
| 811 | + | |
| 812 | + return true; // keep recursing! | |
| 813 | +} | |
| 814 | + | |
| 815 | + | |
| 816 | +// TODO: does cleanupRemovedVoxels() ever get called? | |
| 817 | +// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots??? | |
| 818 | +void VoxelSystem::recreateVoxelGeometryInView() { | |
| 819 | + | |
| 820 | + qDebug() << "recreateVoxelGeometryInView()..."; | |
| 821 | + | |
| 822 | + recreateVoxelGeometryInViewArgs args(this); | |
| 823 | + _writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done | |
| 824 | + _tree->lockForRead(); // don't let anyone change our tree structure until we're run | |
| 825 | + | |
| 826 | + // reset our write arrays bookkeeping to think we've got no voxels in it | |
| 827 | + clearFreeBufferIndexes(); | |
| 828 | + | |
| 829 | + // do we need to reset out _writeVoxelDirtyArray arrays?? | |
| 830 | + memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); | |
| 831 | + | |
| 832 | + _tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args); | |
| 833 | + _tree->unlock(); | |
| 834 | + _writeArraysLock.unlock(); | |
| 835 | +} | |
| 836 | + | |
| 837 | +void VoxelSystem::checkForCulling() { | |
| 762 | 838 | PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()"); |
| 763 | 839 | quint64 start = usecTimestampNow(); |
| 764 | 840 | |
| @@ -786,7 +862,20 @@ void VoxelSystem::checkForCulling() { · 21 unmodified lines | ||
| 786 | 862 | _hasRecentlyChanged = false; |
| 787 | 863 | } |
| 788 | 864 | |
| 789 | − hideOutOfView(forceFullFrustum); | |
| 865 | + // This would be a good place to do a special processing pass, for example, switching the LOD of the scene | |
| 866 | + bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() || | |
| 867 | + _lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust()); | |
| 868 | + | |
| 869 | + // track that these values | |
| 870 | + _lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); | |
| 871 | + _lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); | |
| 872 | + | |
| 873 | + if (fullRedraw) { | |
| 874 | + // this will remove all old geometry and recreate the correct geometry for all in view voxels | |
| 875 | + recreateVoxelGeometryInView(); | |
| 876 | + } else { | |
| 877 | + hideOutOfView(forceFullFrustum); | |
| 878 | + } | |
| 790 | 879 | |
| 791 | 880 | if (forceFullFrustum) { |
| 792 | 881 | quint64 endViewCulling = usecTimestampNow(); |
| @@ -905,12 +994,26 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { · 112 unmodified lines | ||
| 905 | 994 | PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), |
| 906 | 995 | "copyWrittenDataToReadArrays()"); |
| 907 | 996 | |
| 908 | − if (_voxelsDirty && _voxelsUpdated) { | |
| 909 | − if (fullVBOs) { | |
| 910 | − copyWrittenDataToReadArraysFullVBOs(); | |
| 997 | + // attempt to get the writeArraysLock for reading and the readArraysLock for writing | |
| 998 | + // so we can copy from the write to the read... if we fail, that's ok, we'll get it the next | |
| 999 | + // time around, the only side effect is the VBOs won't be updated this frame | |
| 1000 | + const int WAIT_FOR_LOCK_IN_MS = 5; | |
| 1001 | + if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) { | |
| 1002 | + if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { | |
| 1003 | + if (_voxelsDirty && _voxelsUpdated) { | |
| 1004 | + if (fullVBOs) { | |
| 1005 | + copyWrittenDataToReadArraysFullVBOs(); | |
| 1006 | + } else { | |
| 1007 | + copyWrittenDataToReadArraysPartialVBOs(); | |
| 1008 | + } | |
| 1009 | + } | |
| 1010 | + _writeArraysLock.unlock(); | |
| 911 | 1011 | } else { |
| 912 | − copyWrittenDataToReadArraysPartialVBOs(); | |
| 1012 | + qDebug() << "couldn't get _writeArraysLock.LockForRead()..."; | |
| 913 | 1013 | } |
| 1014 | + _readArraysLock.unlock(); | |
| 1015 | + } else { | |
| 1016 | + qDebug() << "couldn't get _readArraysLock.LockForWrite()..."; | |
| 914 | 1017 | } |
| 915 | 1018 | } |
| 916 | 1019 | |
| @@ -1204,18 +1307,28 @@ void VoxelSystem::updateVBOs() { · 287 unmodified lines | ||
| 1204 | 1307 | PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); |
| 1205 | 1308 | if (! _usePrimitiveRenderer) { |
| 1206 | 1309 | if (_voxelsDirty) { |
| 1207 | − if (_readRenderFullVBO) { | |
| 1208 | − updateFullVBOs(); | |
| 1310 | + | |
| 1311 | + // attempt to lock the read arrays, to for copying from them to the actual GPU VBOs. | |
| 1312 | + // if we fail to get the lock, that's ok, our VBOs will update on the next frame... | |
| 1313 | + const int WAIT_FOR_LOCK_IN_MS = 5; | |
| 1314 | + if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { | |
| 1315 | + if (_readRenderFullVBO) { | |
| 1316 | + updateFullVBOs(); | |
| 1317 | + } else { | |
| 1318 | + updatePartialVBOs(); | |
| 1319 | + } | |
| 1320 | + _voxelsDirty = false; | |
| 1321 | + _readRenderFullVBO = false; | |
| 1322 | + _readArraysLock.unlock(); | |
| 1209 | 1323 | } else { |
| 1210 | − updatePartialVBOs(); | |
| 1324 | + qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()"; | |
| 1211 | 1325 | } |
| 1212 | − _voxelsDirty = false; | |
| 1213 | − _readRenderFullVBO = false; | |
| 1214 | 1326 | } |
| 1215 | 1327 | } |
| 1216 | 1328 | _callsToTreesToArrays = 0; // clear it |
| 1217 | 1329 | } |
| 1218 | 1330 | |
| 1331 | +// this should only be called on the main application thread during render | |
| 1219 | 1332 | void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) { |
| 1220 | 1333 | bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); |
| 1221 | 1334 | PerformanceWarning warn(showWarning, "updateVBOSegment()"); |
| @@ -1261,7 +1374,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg · 39 unmodified lines | ||
| 1261 | 1374 | } |
| 1262 | 1375 | } |
| 1263 | 1376 | |
| 1264 | −void VoxelSystem::render(bool texture) { | |
| 1377 | +void VoxelSystem::render() { | |
| 1378 | + bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures); | |
| 1265 | 1379 | bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); |
| 1266 | 1380 | PerformanceWarning warn(showWarnings, "render()"); |
| 1267 | 1381 | |
| @@ -1478,11 +1592,7 @@ void VoxelSystem::killLocalVoxels() { · 210 unmodified lines | ||
| 1478 | 1592 | setupNewVoxelsForDrawing(); |
| 1479 | 1593 | } |
| 1480 | 1594 | |
| 1481 | −void VoxelSystem::redrawInViewVoxels() { | |
| 1482 | − hideOutOfView(true); | |
| 1483 | −} | |
| 1484 | − | |
| 1485 | − | |
| 1595 | +// only called on main thread | |
| 1486 | 1596 | bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) { |
| 1487 | 1597 | _nodeCount++; |
| 1488 | 1598 | VoxelTreeElement* voxel = (VoxelTreeElement*)element; |
| @@ -1491,12 +1601,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void · 2 unmodified lines | ||
| 1491 | 1601 | return true; |
| 1492 | 1602 | } |
| 1493 | 1603 | |
| 1604 | +// only called on main thread, and also always followed by a call to cleanupVoxelMemory() | |
| 1605 | +// you shouldn't be calling this on any other thread or without also cleaning up voxel memory | |
| 1494 | 1606 | void VoxelSystem::clearAllNodesBufferIndex() { |
| 1495 | 1607 | PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), |
| 1496 | 1608 | "VoxelSystem::clearAllNodesBufferIndex()"); |
| 1497 | 1609 | _nodeCount = 0; |
| 1498 | 1610 | _tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read |
| 1499 | 1611 | _tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation); |
| 1612 | + clearFreeBufferIndexes(); // this should be called too | |
| 1500 | 1613 | _tree->unlock(); |
| 1501 | 1614 | if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { |
| 1502 | 1615 | qDebug("clearing buffer index of %d nodes", _nodeCount); |