// // VoxelSystem.cpp // // Created by Philip on 12/31/12. // Copyright (c) 2012 High Fidelity, Inc. All rights reserved. // #include #include #include // to load voxels from file #include // to load voxels from file #include #include #include #include #include #include "Application.h" #include "InterfaceConfig.h" #include "Menu.h" #include "renderer/ProgramObject.h" #include "VoxelConstants.h" #include "VoxelSystem.h" const bool VoxelSystem::DONT_BAIL_EARLY = false; float identityVerticesGlobalNormals[] = { 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1 }; float identityVertices[] = { 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1, //0-7 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1, //8-15 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1 }; // 16-23 GLfloat identityNormals[] = { 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,+1, 0,0,+1, 0,0,+1, 0,0,+1, 0,-1,0, 0,-1,0, 0,+1,0, 0,+1,0, 0,-1,0, 0,-1,0, 0,+1,0, 0,+1,0, -1,0,0, +1,0,0, +1,0,0, -1,0,0, -1,0,0, +1,0,0, +1,0,0, -1,0,0 }; GLubyte identityIndices[] = { 0,2,1, 0,3,2, // Z- 8,9,13, 8,13,12, // Y- 16,23,19, 16,20,23, // X- 17,18,22, 17,22,21, // X+ 10,11,15, 10,15,14, // Y+ 4,5,6, 4,6,7 }; // Z+ GLubyte identityIndicesTop[] = { 2, 3, 7, 2, 7, 6 }; GLubyte identityIndicesBottom[] = { 0, 1, 5, 0, 5, 4 }; GLubyte identityIndicesLeft[] = { 0, 7, 3, 0, 4, 7 }; GLubyte identityIndicesRight[] = { 1, 2, 6, 1, 6, 5 }; GLubyte identityIndicesFront[] = { 0, 2, 1, 0, 3, 2 }; GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 }; VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) : NodeData(), _treeScale(treeScale), _maxVoxels(maxVoxels), _initialized(false), _writeArraysLock(QReadWriteLock::Recursive), _readArraysLock(QReadWriteLock::Recursive), _inOcclusions(false), _showCulledSharedFaces(false), _usePrimitiveRenderer(false), _renderer(0) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; _writeRenderFullVBO = true; _readRenderFullVBO = true; _tree = (tree) ? tree : new VoxelTree(); _tree->getRoot()->setVoxelSystem(this); VoxelTreeElement::addDeleteHook(this); VoxelTreeElement::addUpdateHook(this); _abandonedVBOSlots = 0; _falseColorizeBySource = false; _dataSourceUUID = QUuid(); _voxelServerCount = 0; _viewFrustum = Application::getInstance()->getViewFrustum(); _useVoxelShader = false; _voxelsAsPoints = false; _voxelShaderModeWhenVoxelsAsPointsEnabled = false; _writeVoxelShaderData = NULL; _readVoxelShaderData = NULL; _readVerticesArray = NULL; _writeVerticesArray = NULL; _readColorsArray = NULL; _writeColorsArray = NULL; _writeVoxelDirtyArray = NULL; _readVoxelDirtyArray = NULL; _inSetupNewVoxelsForDrawing = false; _useFastVoxelPipeline = false; _culledOnce = false; _inhideOutOfView = false; _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE; _lastKnownBoundaryLevelAdjust = 0; } void VoxelSystem::elementDeleted(OctreeElement* element) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; if (voxel->getVoxelSystem() == this) { if ((_voxelsInWriteArrays != 0) || _usePrimitiveRenderer) { forceRemoveNodeFromArrays(voxel); } else { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { printf("VoxelSystem::elementDeleted() while _voxelsInWriteArrays==0, is that expected? \n"); } } } } void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) { _useFastVoxelPipeline = !disableFastVoxelPipeline; setupNewVoxelsForDrawing(); } void VoxelSystem::elementUpdated(OctreeElement* element) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; // If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail.. if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) { return; } if (voxel->getVoxelSystem() == this) { bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); shouldRender = voxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); if (voxel->getShouldRender() != shouldRender) { voxel->setShouldRender(shouldRender); } if (!voxel->isLeaf()) { // As we check our children, see if any of them went from shouldRender to NOT shouldRender // then we probably dropped LOD and if we don't have color, we want to average our children // for a new color. int childrenGotHiddenCount = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelTreeElement* childVoxel = voxel->getChildAtIndex(i); if (childVoxel) { bool wasShouldRender = childVoxel->getShouldRender(); bool isShouldRender = childVoxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); if (wasShouldRender && !isShouldRender) { childrenGotHiddenCount++; } } } if (childrenGotHiddenCount > 0) { voxel->calculateAverageFromChildren(); } } const bool REUSE_INDEX = true; const bool DONT_FORCE_REDRAW = false; updateNodeInArrays(voxel, REUSE_INDEX, DONT_FORCE_REDRAW); _voxelsUpdated++; voxel->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. setupNewVoxelsForDrawingSingleNode(); } } // returns an available index, starts by reusing a previously freed index, but if there isn't one available // it will use the end of the VBO array and grow our accounting of that array. // and makes the index available for some other node to use glBufferIndex VoxelSystem::getNextBufferIndex() { glBufferIndex output = GLBUFFER_INDEX_UNKNOWN; // if there's a free index, use it... if (_freeIndexes.size() > 0) { _freeIndexLock.lock(); output = _freeIndexes.back(); _freeIndexes.pop_back(); _freeIndexLock.unlock(); } else { output = _voxelsInWriteArrays; _voxelsInWriteArrays++; } return output; } // Release responsibility of the buffer/vbo index from the VoxelTreeElement, and makes the index available for some other node to use // will also "clean up" the index data for the buffer/vbo slot, so that if it's in the middle of the draw range, the triangles // will be "invisible" void VoxelSystem::freeBufferIndex(glBufferIndex index) { if (_voxelsInWriteArrays == 0) { qDebug() << "freeBufferIndex() called when _voxelsInWriteArrays == 0!"; } // make the index available for next node that needs to be drawn _freeIndexLock.lock(); _freeIndexes.push_back(index); _freeIndexLock.unlock(); // make the VBO slot "invisible" in case this slot is not used const glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX); const float voxelScale = 0; const nodeColor BLACK = {0, 0, 0, 0}; updateArraysDetails(index, startVertex, voxelScale, BLACK); } // This will run through the list of _freeIndexes and reset their VBO array values to be "invisible". void VoxelSystem::clearFreeBufferIndexes() { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "clearFreeBufferIndexes()"); _voxelsInWriteArrays = 0; // reset our VBO _abandonedVBOSlots = 0; // clear out freeIndexes { PerformanceWarning warn(showWarnings,"clearFreeBufferIndexes() : _freeIndexLock.lock()"); _freeIndexLock.lock(); } { PerformanceWarning warn(showWarnings,"clearFreeBufferIndexes() : _freeIndexes.clear()"); _freeIndexes.clear(); } _freeIndexLock.unlock(); } VoxelSystem::~VoxelSystem() { VoxelTreeElement::removeDeleteHook(this); VoxelTreeElement::removeUpdateHook(this); cleanupVoxelMemory(); 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; } bool wasInitialized = _initialized; if (wasInitialized) { clearAllNodesBufferIndex(); cleanupVoxelMemory(); } _maxVoxels = maxVoxels; if (wasInitialized) { initVoxelMemory(); } if (wasInitialized) { forceRedrawEntireTree(); } } // 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; } bool wasInitialized = _initialized; if (wasInitialized) { clearAllNodesBufferIndex(); cleanupVoxelMemory(); } _useVoxelShader = useVoxelShader; _usePrimitiveRenderer = false; if (wasInitialized) { initVoxelMemory(); } if (wasInitialized) { forceRedrawEntireTree(); } } void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) { if (_voxelsAsPoints == voxelsAsPoints) { return; } bool wasInitialized = _initialized; // If we're "turning on" Voxels as points, we need to double check that we're in voxel shader mode. // Voxels as points uses the VoxelShader memory model, so if we're not in voxel shader mode, // then set it to voxel shader mode. if (voxelsAsPoints) { Menu::getInstance()->getUseVoxelShader()->setEnabled(false); // If enabling this... then do it before checking voxel shader status, that way, if voxel // shader is already enabled, we just start drawing as points. _voxelsAsPoints = true; if (!_useVoxelShader) { setUseVoxelShader(true); _voxelShaderModeWhenVoxelsAsPointsEnabled = false; } else { _voxelShaderModeWhenVoxelsAsPointsEnabled = true; } } else { Menu::getInstance()->getUseVoxelShader()->setEnabled(true); // if we're turning OFF voxels as point mode, then we check what the state of voxel shader was when we enabled // voxels as points, if it was OFF, then we return it to that value. if (_voxelShaderModeWhenVoxelsAsPointsEnabled == false) { setUseVoxelShader(false); } // If disabling this... then do it AFTER checking previous voxel shader status, that way, if voxel // shader is was not enabled, we switch back to normal mode before turning off points. _voxelsAsPoints = false; } // Set our voxels as points if (wasInitialized) { forceRedrawEntireTree(); } } void VoxelSystem::cleanupVoxelMemory() { if (_initialized) { _readArraysLock.lockForWrite(); _initialized = false; // no longer initialized if (_useVoxelShader) { // these are used when in VoxelShader mode. glDeleteBuffers(1, &_vboVoxelsID); glDeleteBuffers(1, &_vboVoxelsIndicesID); delete[] _writeVoxelShaderData; delete[] _readVoxelShaderData; _writeVoxelShaderData = _readVoxelShaderData = NULL; } else { // Destroy glBuffers glDeleteBuffers(1, &_vboVerticesID); glDeleteBuffers(1, &_vboColorsID); glDeleteBuffers(1, &_vboIndicesTop); glDeleteBuffers(1, &_vboIndicesBottom); glDeleteBuffers(1, &_vboIndicesLeft); glDeleteBuffers(1, &_vboIndicesRight); glDeleteBuffers(1, &_vboIndicesFront); glDeleteBuffers(1, &_vboIndicesBack); delete[] _readVerticesArray; delete[] _writeVerticesArray; delete[] _readColorsArray; delete[] _writeColorsArray; _readVerticesArray = NULL; _writeVerticesArray = NULL; _readColorsArray = NULL; _writeColorsArray = NULL; } delete _renderer; _renderer = 0; delete[] _writeVoxelDirtyArray; delete[] _readVoxelDirtyArray; _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; _readArraysLock.unlock(); } } void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndices[]) { GLuint* indicesArray = new GLuint[INDICES_PER_FACE * _maxVoxels]; // populate the indicesArray // this will not change given new voxels, so we can set it all up now for (unsigned long n = 0; n < _maxVoxels; n++) { // fill the indices array int voxelIndexOffset = n * INDICES_PER_FACE; GLuint* currentIndicesPos = indicesArray + voxelIndexOffset; int startIndex = (n * GLOBAL_NORMALS_VERTICES_PER_VOXEL); for (int i = 0; i < INDICES_PER_FACE; i++) { // add indices for this side of the cube currentIndicesPos[i] = startIndex + faceIdentityIndices[i]; } } glGenBuffers(1, &faceVBOID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, faceVBOID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDICES_PER_FACE * sizeof(GLuint) * _maxVoxels, indicesArray, GL_STATIC_DRAW); _memoryUsageVBO += INDICES_PER_FACE * sizeof(GLuint) * _maxVoxels; // delete the indices and normals arrays that are no longer needed delete[] indicesArray; } void VoxelSystem::initVoxelMemory() { _readArraysLock.lockForWrite(); _writeArraysLock.lockForWrite(); _memoryUsageRAM = 0; _memoryUsageVBO = 0; // our VBO allocations as we know them // if _voxelsAsPoints then we must have _useVoxelShader if (_voxelsAsPoints && !_useVoxelShader) { _useVoxelShader = true; } if (_useVoxelShader) { GLuint* indicesArray = new GLuint[_maxVoxels]; // populate the indicesArray // this will not change given new voxels, so we can set it all up now for (unsigned long n = 0; n < _maxVoxels; n++) { indicesArray[n] = n; } // bind the indices VBO to the actual indices array glGenBuffers(1, &_vboVoxelsIndicesID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboVoxelsIndicesID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * _maxVoxels, indicesArray, GL_STATIC_DRAW); _memoryUsageVBO += sizeof(GLuint) * _maxVoxels; glGenBuffers(1, &_vboVoxelsID); glBindBuffer(GL_ARRAY_BUFFER, _vboVoxelsID); glBufferData(GL_ARRAY_BUFFER, _maxVoxels * sizeof(VoxelShaderVBOData), NULL, GL_DYNAMIC_DRAW); _memoryUsageVBO += _maxVoxels * sizeof(VoxelShaderVBOData); // delete the indices and normals arrays that are no longer needed delete[] indicesArray; // we will track individual dirty sections with these arrays of bools _writeVoxelDirtyArray = new bool[_maxVoxels]; memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); _memoryUsageRAM += (_maxVoxels * sizeof(bool)); _readVoxelDirtyArray = new bool[_maxVoxels]; memset(_readVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); _memoryUsageRAM += (_maxVoxels * sizeof(bool)); // prep the data structures for incoming voxel data _writeVoxelShaderData = new VoxelShaderVBOData[_maxVoxels]; _memoryUsageRAM += (sizeof(VoxelShaderVBOData) * _maxVoxels); _readVoxelShaderData = new VoxelShaderVBOData[_maxVoxels]; _memoryUsageRAM += (sizeof(VoxelShaderVBOData) * _maxVoxels); } else { // Global Normals mode uses a technique of not including normals on any voxel vertices, and instead // rendering the voxel faces in 6 passes that use a global call to glNormal3f() setupFaceIndices(_vboIndicesTop, identityIndicesTop); setupFaceIndices(_vboIndicesBottom, identityIndicesBottom); setupFaceIndices(_vboIndicesLeft, identityIndicesLeft); setupFaceIndices(_vboIndicesRight, identityIndicesRight); setupFaceIndices(_vboIndicesFront, identityIndicesFront); setupFaceIndices(_vboIndicesBack, identityIndicesBack); // Depending on if we're using per vertex normals, we will need more or less vertex points per voxel int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; glGenBuffers(1, &_vboVerticesID); glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); glBufferData(GL_ARRAY_BUFFER, vertexPointsPerVoxel * sizeof(GLfloat) * _maxVoxels, NULL, GL_DYNAMIC_DRAW); _memoryUsageVBO += vertexPointsPerVoxel * sizeof(GLfloat) * _maxVoxels; // VBO for colorsArray glGenBuffers(1, &_vboColorsID); glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); glBufferData(GL_ARRAY_BUFFER, vertexPointsPerVoxel * sizeof(GLubyte) * _maxVoxels, NULL, GL_DYNAMIC_DRAW); _memoryUsageVBO += vertexPointsPerVoxel * sizeof(GLubyte) * _maxVoxels; // we will track individual dirty sections with these arrays of bools _writeVoxelDirtyArray = new bool[_maxVoxels]; memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); _memoryUsageRAM += (sizeof(bool) * _maxVoxels); _readVoxelDirtyArray = new bool[_maxVoxels]; memset(_readVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); _memoryUsageRAM += (sizeof(bool) * _maxVoxels); // prep the data structures for incoming voxel data _writeVerticesArray = new GLfloat[vertexPointsPerVoxel * _maxVoxels]; _memoryUsageRAM += (sizeof(GLfloat) * vertexPointsPerVoxel * _maxVoxels); _readVerticesArray = new GLfloat[vertexPointsPerVoxel * _maxVoxels]; _memoryUsageRAM += (sizeof(GLfloat) * vertexPointsPerVoxel * _maxVoxels); _writeColorsArray = new GLubyte[vertexPointsPerVoxel * _maxVoxels]; _memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels); _readColorsArray = new GLubyte[vertexPointsPerVoxel * _maxVoxels]; _memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels); // create our simple fragment shader if we're the first system to init if (!_perlinModulateProgram.isLinked()) { _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/perlin_modulate.vert"); _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/perlin_modulate.frag"); _perlinModulateProgram.link(); _perlinModulateProgram.bind(); _perlinModulateProgram.setUniformValue("permutationNormalTexture", 0); _perlinModulateProgram.release(); _shadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/shadow_map.frag"); _shadowMapProgram.link(); _shadowMapProgram.bind(); _shadowMapProgram.setUniformValue("shadowMap", 0); _shadowMapProgram.release(); } } _renderer = new PrimitiveRenderer(_maxVoxels); _initialized = true; _writeArraysLock.unlock(); _readArraysLock.unlock(); } int VoxelSystem::parseData(const QByteArray& packet) { bool showTimingDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData()",showTimingDetails); PacketType command = packetTypeForPacket(packet); int numBytesPacketHeader = numBytesForPacketHeader(packet); switch(command) { case PacketTypeVoxelData: { PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() PacketType_VOXEL_DATA part...",showTimingDetails); const unsigned char* dataAt = reinterpret_cast(packet.data()) + numBytesPacketHeader; OCTREE_PACKET_FLAGS flags = (*(OCTREE_PACKET_FLAGS*)(dataAt)); dataAt += sizeof(OCTREE_PACKET_FLAGS); OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); dataAt += sizeof(OCTREE_PACKET_SEQUENCE); OCTREE_PACKET_SENT_TIME sentAt = (*(OCTREE_PACKET_SENT_TIME*)dataAt); dataAt += sizeof(OCTREE_PACKET_SENT_TIME); bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); int flightTime = arrivedAt - sentAt; OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionLength = 0; int dataBytes = packet.size() - (numBytesPacketHeader + OCTREE_PACKET_EXTRA_HEADERS_SIZE); int subsection = 1; while (dataBytes > 0) { if (packetIsCompressed) { if (dataBytes > sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { sectionLength = (*(OCTREE_PACKET_INTERNAL_SECTION_SIZE*)dataAt); dataAt += sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); dataBytes -= sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); } else { sectionLength = 0; dataBytes = 0; // stop looping something is wrong } } else { sectionLength = dataBytes; } if (sectionLength) { PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() section"); // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceUUID()); _tree->lockForWrite(); OctreePacketData packetData(packetIsCompressed); packetData.loadFinalizedContent(dataAt, sectionLength); if (Application::getInstance()->getLogger()->extraDebugging()) { qDebug("VoxelSystem::parseData() ... Got Packet Section" " color:%s compressed:%s sequence: %u flight:%d usec size:%d data:%d" " subsection:%d sectionLength:%d uncompressed:%d", debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed), sequence, flightTime, packet.size(), dataBytes, subsection, sectionLength, packetData.getUncompressedSize()); } _tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); _tree->unlock(); dataBytes -= sectionLength; dataAt += sectionLength; } } subsection++; } default: break; } if (!_useFastVoxelPipeline || _writeRenderFullVBO) { setupNewVoxelsForDrawing(); } else { setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY); } Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(packet.size()); return packet.size(); } void VoxelSystem::setupNewVoxelsForDrawing() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "setupNewVoxelsForDrawing()"); if (!_initialized) { return; // bail early if we're not initialized } quint64 start = usecTimestampNow(); quint64 sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) { return; // bail early, it hasn't been long enough since the last time we ran } _inSetupNewVoxelsForDrawing = true; bool didWriteFullVBO = _writeRenderFullVBO; if (_tree->isDirty()) { static char buffer[64] = { 0 }; if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", debug::valueOf(_writeRenderFullVBO)); }; PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); _callsToTreesToArrays++; if (_writeRenderFullVBO) { if (_usePrimitiveRenderer) { _renderer->release(); clearAllNodesBufferIndex(); } clearFreeBufferIndexes(); } _voxelsUpdated = newTreeToArrays(_tree->getRoot()); _tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean if (_writeRenderFullVBO) { _abandonedVBOSlots = 0; // reset the count of our abandoned slots, why is this here and not earlier???? } _writeRenderFullVBO = false; } else { _voxelsUpdated = 0; } if (_usePrimitiveRenderer) { if (_voxelsUpdated) { _voxelsDirty=true; } } else { // lock on the buffer write lock so we can't modify the data when the GPU is reading it _readArraysLock.lockForWrite(); if (_voxelsUpdated) { _voxelsDirty=true; } // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated copyWrittenDataToReadArrays(didWriteFullVBO); _readArraysLock.unlock(); } quint64 end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; _inSetupNewVoxelsForDrawing = false; bool extraDebugging = Application::getInstance()->getLogger()->extraDebugging(); if (extraDebugging) { qDebug("setupNewVoxelsForDrawing()... _voxelsUpdated=%lu...",_voxelsUpdated); _viewFrustum->printDebugDetails(); } } void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "setupNewVoxelsForDrawingSingleNode() xxxxx"); quint64 start = usecTimestampNow(); quint64 sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging if (allowBailEarly && !iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) { return; // bail early, it hasn't been long enough since the last time we ran } if (_usePrimitiveRenderer) { _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty _voxelsUpdated = 0; } else { // 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();" ); _readArraysLock.lockForWrite(); } _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated copyWrittenDataToReadArrays(_writeRenderFullVBO); // after... _voxelsUpdated = 0; _readArraysLock.unlock(); } quint64 end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; } 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(); // track how long its been since we were last moving. If we have recently moved then only use delta frustums, if // it's been a long time since we last moved, then go ahead and do a full frustum cull. if (isViewChanging()) { _lastViewIsChanging = start; } quint64 sinceLastMoving = (start - _lastViewIsChanging) / 1000; bool enoughTime = (sinceLastMoving >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)); // These has changed events will occur before we stop. So we need to remember this for when we finally have stopped // moving long enough to be enoughTime if (hasViewChanged()) { _hasRecentlyChanged = true; } // If we have recently changed, but it's been enough time since we last moved, then we will do a full frustum // hide/show culling pass bool forceFullFrustum = enoughTime && _hasRecentlyChanged; // in hide mode, we only track the full frustum culls, because we don't care about the partials. if (forceFullFrustum) { _lastViewCulling = start; _hasRecentlyChanged = false; } // 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()); _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(); _lastViewCullingElapsed = (endViewCulling - start) / 1000; } // Once we call cleanupRemovedVoxels() we do need to rebuild our VBOs (if anything was actually removed). So, // we should consider putting this someplace else... as this might be able to occur less frequently, and save us on // VBO reubuilding. Possibly we should do this only if our actual VBO usage crosses some lower boundary. cleanupRemovedVoxels(); } void VoxelSystem::cleanupRemovedVoxels() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "cleanupRemovedVoxels()"); // This handles cleanup of voxels that were culled as part of our regular out of view culling operation if (!_removedVoxels.isEmpty()) { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { qDebug() << "cleanupRemovedVoxels().. _removedVoxels=" << _removedVoxels.count(); } while (!_removedVoxels.isEmpty()) { delete _removedVoxels.extract(); } _writeRenderFullVBO = true; // if we remove voxels, we must update our full VBOs } // we also might have VBO slots that have been abandoned, if too many of our VBO slots // are abandonded we want to rerender our full VBOs const float TOO_MANY_ABANDONED_RATIO = 0.5f; if (!_usePrimitiveRenderer && !_writeRenderFullVBO && (_abandonedVBOSlots > (_voxelsInWriteArrays * TOO_MANY_ABANDONED_RATIO))) { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { qDebug() << "cleanupRemovedVoxels().. _abandonedVBOSlots [" << _abandonedVBOSlots << "] > TOO_MANY_ABANDONED_RATIO"; } _writeRenderFullVBO = true; } } void VoxelSystem::copyWrittenDataToReadArraysFullVBOs() { copyWrittenDataSegmentToReadArrays(0, _voxelsInWriteArrays - 1); _voxelsInReadArrays = _voxelsInWriteArrays; // clear our dirty flags memset(_writeVoxelDirtyArray, false, _voxelsInWriteArrays * sizeof(bool)); // let the reader know to get the full array _readRenderFullVBO = true; } void VoxelSystem::copyWrittenDataToReadArraysPartialVBOs() { glBufferIndex segmentStart = 0; bool inSegment = false; for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) { bool thisVoxelDirty = _writeVoxelDirtyArray[i]; _readVoxelDirtyArray[i] |= thisVoxelDirty; _writeVoxelDirtyArray[i] = false; if (!inSegment) { if (thisVoxelDirty) { segmentStart = i; inSegment = true; } } else { if (!thisVoxelDirty) { // If we got here because because this voxel is NOT dirty, so the last dirty voxel was the one before // this one and so that's where the "segment" ends copyWrittenDataSegmentToReadArrays(segmentStart, i - 1); inSegment = false; } } } // if we got to the end of the array, and we're in an active dirty segment... if (inSegment) { copyWrittenDataSegmentToReadArrays(segmentStart, _voxelsInWriteArrays - 1); } // update our length _voxelsInReadArrays = _voxelsInWriteArrays; } void VoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd) { int segmentLength = (segmentEnd - segmentStart) + 1; if (_useVoxelShader) { GLsizeiptr segmentSizeBytes = segmentLength * sizeof(VoxelShaderVBOData); void* readDataAt = &_readVoxelShaderData[segmentStart]; void* writeDataAt = &_writeVoxelShaderData[segmentStart]; memcpy(readDataAt, writeDataAt, segmentSizeBytes); } else { // Depending on if we're using per vertex normals, we will need more or less vertex points per voxel int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; GLintptr segmentStartAt = segmentStart * vertexPointsPerVoxel * sizeof(GLfloat); GLsizeiptr segmentSizeBytes = segmentLength * vertexPointsPerVoxel * sizeof(GLfloat); GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * vertexPointsPerVoxel); GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * vertexPointsPerVoxel); memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes); segmentStartAt = segmentStart * vertexPointsPerVoxel * sizeof(GLubyte); segmentSizeBytes = segmentLength * vertexPointsPerVoxel * sizeof(GLubyte); GLubyte* readColorsAt = _readColorsArray + (segmentStart * vertexPointsPerVoxel); GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * vertexPointsPerVoxel); memcpy(readColorsAt, writeColorsAt, segmentSizeBytes); } } void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "copyWrittenDataToReadArrays()"); // 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 { qDebug() << "couldn't get _writeArraysLock.LockForRead()..."; } _readArraysLock.unlock(); } else { qDebug() << "couldn't get _readArraysLock.LockForWrite()..."; } } int VoxelSystem::newTreeToArrays(VoxelTreeElement* voxel) { int voxelsUpdated = 0; bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();; int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); shouldRender = voxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); voxel->setShouldRender(shouldRender); // let children figure out their renderness if (!voxel->isLeaf()) { // As we check our children, see if any of them went from shouldRender to NOT shouldRender // then we probably dropped LOD and if we don't have color, we want to average our children // for a new color. int childrenGotHiddenCount = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelTreeElement* childVoxel = voxel->getChildAtIndex(i); if (childVoxel) { bool wasShouldRender = childVoxel->getShouldRender(); voxelsUpdated += newTreeToArrays(childVoxel); bool isShouldRender = childVoxel->getShouldRender(); if (wasShouldRender && !isShouldRender) { childrenGotHiddenCount++; } } } if (childrenGotHiddenCount > 0) { voxel->calculateAverageFromChildren(); } } // update their geometry in the array. depending on our over all mode (fullVBO or not) we will reuse or not reuse the index if (_writeRenderFullVBO) { const bool DONT_REUSE_INDEX = false; const bool FORCE_REDRAW = true; voxelsUpdated += updateNodeInArrays(voxel, DONT_REUSE_INDEX, FORCE_REDRAW); } else { const bool REUSE_INDEX = true; const bool DONT_FORCE_REDRAW = false; voxelsUpdated += updateNodeInArrays(voxel, REUSE_INDEX, DONT_FORCE_REDRAW); } voxel->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. return voxelsUpdated; } // called as response to elementDeleted() in fast pipeline case. The node // is being deleted, but it's state is such that it thinks it should render // and therefore we can't use the normal render calculations. This method // will forcibly remove it from the VBOs because we know better!!! int VoxelSystem::forceRemoveNodeFromArrays(VoxelTreeElement* node) { if (!_initialized) { return 0; } if (_usePrimitiveRenderer) { if (node->isKnownBufferIndex()) { int primitiveIndex = node->getBufferIndex(); _renderer->remove(primitiveIndex); node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); return 1; } } else { // if the node is not in the VBOs then we have nothing to do! if (node->isKnownBufferIndex()) { // If this node has not yet been written to the array, then add it to the end of the array. glBufferIndex nodeIndex = node->getBufferIndex(); node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible! return 1; // updated! } } return 0; // not-updated } int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels && (_freeIndexes.size() == 0)) { // We need to think about what else we can do in this case. This basically means that all of our available // VBO slots are used up, but we're trying to render more voxels. At this point, if this happens we'll just // not render these Voxels. We need to think about ways to keep the entire scene intact but maybe lower quality // possibly shifting down to lower LOD or something. This debug message is to help identify, if/when/how this // state actually occurs. if (Application::getInstance()->getLogger()->extraDebugging()) { qDebug("OH NO! updateNodeInArrays() BAILING (_voxelsInWriteArrays >= _maxVoxels)"); } return 0; } if (!_initialized) { return 0; } // If we've changed any attributes (our renderness, our color, etc), or we've been told to force a redraw // then update the Arrays... if (forceDraw || node->isDirty()) { // If we're should render, use our legit location and scale, if (node->getShouldRender()) { glm::vec3 startVertex = node->getCorner(); float voxelScale = node->getScale(); nodeColor const & color = node->getColor(); if (_usePrimitiveRenderer) { if (node->isKnownBufferIndex()) { int primitiveIndex = node->getBufferIndex(); _renderer->remove(primitiveIndex); node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); } else { node->setVoxelSystem(this); } unsigned char occlusions; if (_showCulledSharedFaces) { occlusions = ~node->getInteriorOcclusions(); } else { occlusions = node->getInteriorOcclusions(); } if (occlusions != OctreeElement::HalfSpace::All) { Cube* cube = new Cube( startVertex.x, startVertex.y, startVertex.z, voxelScale, color[RED_INDEX], color[GREEN_INDEX], color[BLUE_INDEX], occlusions); if (cube) { int primitiveIndex = _renderer->add(cube); node->setBufferIndex(primitiveIndex); } } } else { glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN; if (reuseIndex && node->isKnownBufferIndex()) { nodeIndex = node->getBufferIndex(); } else { nodeIndex = getNextBufferIndex(); node->setBufferIndex(nodeIndex); node->setVoxelSystem(this); } // populate the array with points for the 8 vertices and RGB color for each added vertex updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor()); } return 1; // updated! } else { // If we shouldn't render, and we're in reuseIndex mode, then free our index, this only operates // on nodes with known index values, so it's safe to call for any node. if (reuseIndex) { return forceRemoveNodeFromArrays(node); } } } return 0; // not-updated } void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { if (_initialized && nodeIndex <= _maxVoxels) { _writeVoxelDirtyArray[nodeIndex] = true; if (_useVoxelShader) { if (_writeVoxelShaderData) { VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex]; writeVerticesAt->x = startVertex.x * TREE_SCALE; writeVerticesAt->y = startVertex.y * TREE_SCALE; writeVerticesAt->z = startVertex.z * TREE_SCALE; writeVerticesAt->s = voxelScale * TREE_SCALE; writeVerticesAt->r = color[RED_INDEX]; writeVerticesAt->g = color[GREEN_INDEX]; writeVerticesAt->b = color[BLUE_INDEX]; } } else { if (_writeVerticesArray && _writeColorsArray) { int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; for (int j = 0; j < vertexPointsPerVoxel; j++ ) { GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * vertexPointsPerVoxel); GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * vertexPointsPerVoxel); *(writeVerticesAt+j) = startVertex[j % 3] + (identityVerticesGlobalNormals[j] * voxelScale); *(writeColorsAt +j) = color[j % 3]; } } } } } glm::vec3 VoxelSystem::computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const { const float* identityVertex = identityVertices + index * 3; return startVertex + glm::vec3(identityVertex[0], identityVertex[1], identityVertex[2]) * voxelScale; } ProgramObject VoxelSystem::_perlinModulateProgram; ProgramObject VoxelSystem::_shadowMapProgram; void VoxelSystem::init() { if (_initialized) { qDebug("[ERROR] VoxelSystem is already initialized."); return; } _callsToTreesToArrays = 0; _setupNewVoxelsForDrawingLastFinished = 0; _setupNewVoxelsForDrawingLastElapsed = 0; _lastViewCullingElapsed = _lastViewCulling = _lastAudit = _lastViewIsChanging = 0; _hasRecentlyChanged = false; _voxelsDirty = false; _voxelsInWriteArrays = 0; _voxelsInReadArrays = 0; // VBO for the verticesArray _initialMemoryUsageGPU = getFreeMemoryGPU(); initVoxelMemory(); // our own _removedVoxels doesn't need to be notified of voxel deletes VoxelTreeElement::removeDeleteHook(&_removedVoxels); } void VoxelSystem::changeTree(VoxelTree* newTree) { disconnect(_tree, 0, this, 0); _tree = newTree; _tree->setDirtyBit(); _tree->getRoot()->setVoxelSystem(this); setupNewVoxelsForDrawing(); } void VoxelSystem::updateFullVBOs() { bool outputWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(outputWarning, "updateFullVBOs()"); { static char buffer[128] = { 0 }; if (outputWarning) { sprintf(buffer, "updateFullVBOs() : updateVBOSegment(0, _voxelsInReadArrays=%lu);", _voxelsInReadArrays); }; PerformanceWarning warn(outputWarning,buffer); updateVBOSegment(0, _voxelsInReadArrays); } { PerformanceWarning warn(outputWarning,"updateFullVBOs() : memset(_readVoxelDirtyArray...)"); // consider the _readVoxelDirtyArray[] clean! memset(_readVoxelDirtyArray, false, _voxelsInReadArrays * sizeof(bool)); } } void VoxelSystem::updatePartialVBOs() { glBufferIndex segmentStart = 0; bool inSegment = false; for (glBufferIndex i = 0; i < _voxelsInReadArrays; i++) { bool thisVoxelDirty = _readVoxelDirtyArray[i]; if (!inSegment) { if (thisVoxelDirty) { segmentStart = i; inSegment = true; _readVoxelDirtyArray[i] = false; // consider us clean! } } else { if (!thisVoxelDirty) { // If we got here because because this voxel is NOT dirty, so the last dirty voxel was the one before // this one and so that's where the "segment" ends updateVBOSegment(segmentStart, i - 1); inSegment = false; } _readVoxelDirtyArray[i] = false; // consider us clean! } } // if we got to the end of the array, and we're in an active dirty segment... if (inSegment) { updateVBOSegment(segmentStart, _voxelsInReadArrays - 1); inSegment = false; } } void VoxelSystem::updateVBOs() { static char buffer[40] = { 0 }; if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { sprintf(buffer, "updateVBOs() _readRenderFullVBO=%s", debug::valueOf(_readRenderFullVBO)); }; // would like to include _callsToTreesToArrays PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); if (! _usePrimitiveRenderer) { if (_voxelsDirty) { // 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 { qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()"; } } } _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()"); if (_useVoxelShader) { int segmentLength = (segmentEnd - segmentStart) + 1; GLintptr segmentStartAt = segmentStart * sizeof(VoxelShaderVBOData); GLsizeiptr segmentSizeBytes = segmentLength * sizeof(VoxelShaderVBOData); void* readVerticesFrom = &_readVoxelShaderData[segmentStart]; glBindBuffer(GL_ARRAY_BUFFER, _vboVoxelsID); glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom); } else { int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; int segmentLength = (segmentEnd - segmentStart) + 1; GLintptr segmentStartAt = segmentStart * vertexPointsPerVoxel * sizeof(GLfloat); GLsizeiptr segmentSizeBytes = segmentLength * vertexPointsPerVoxel * sizeof(GLfloat); GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * vertexPointsPerVoxel); { PerformanceWarning warn(showWarning, "updateVBOSegment() : glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID);"); glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); } { PerformanceWarning warn(showWarning, "updateVBOSegment() : glBufferSubData() _vboVerticesID);"); glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom); } segmentStartAt = segmentStart * vertexPointsPerVoxel * sizeof(GLubyte); segmentSizeBytes = segmentLength * vertexPointsPerVoxel * sizeof(GLubyte); GLubyte* readColorsFrom = _readColorsArray + (segmentStart * vertexPointsPerVoxel); { PerformanceWarning warn(showWarning, "updateVBOSegment() : glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);"); glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); } { PerformanceWarning warn(showWarning, "updateVBOSegment() : glBufferSubData() _vboColorsID);"); glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom); } } } void VoxelSystem::render() { bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "render()"); // If we got here and we're not initialized then bail! if (!_initialized) { return; } updateVBOs(); // if not don't... then do... if (_useVoxelShader) { PerformanceWarning warn(showWarnings,"render().. _useVoxelShader openGL.."); //Define this somewhere in your header file #define BUFFER_OFFSET(i) ((void*)(i)) glBindBuffer(GL_ARRAY_BUFFER, _vboVoxelsID); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, sizeof(VoxelShaderVBOData), BUFFER_OFFSET(0)); //The starting point of the VBO, for the vertices int attributeLocation; if (!_voxelsAsPoints) { Application::getInstance()->getVoxelShader().begin(); attributeLocation = Application::getInstance()->getVoxelShader().attributeLocation("voxelSizeIn"); glEnableVertexAttribArray(attributeLocation); glVertexAttribPointer(attributeLocation, 1, GL_FLOAT, false, sizeof(VoxelShaderVBOData), BUFFER_OFFSET(3*sizeof(float))); } else { glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glm::vec2 viewDimensions = Application::getInstance()->getViewportDimensions(); float viewportWidth = viewDimensions.x; float viewportHeight = viewDimensions.y; glm::vec3 cameraPosition = Application::getInstance()->getViewFrustum()->getPosition(); PointShader& pointShader = Application::getInstance()->getPointShader(); pointShader.begin(); pointShader.setUniformValue(pointShader.uniformLocation("viewportWidth"), viewportWidth); pointShader.setUniformValue(pointShader.uniformLocation("viewportHeight"), viewportHeight); pointShader.setUniformValue(pointShader.uniformLocation("cameraPosition"), cameraPosition); attributeLocation = pointShader.attributeLocation("voxelSizeIn"); glEnableVertexAttribArray(attributeLocation); glVertexAttribPointer(attributeLocation, 1, GL_FLOAT, false, sizeof(VoxelShaderVBOData), BUFFER_OFFSET(3*sizeof(float))); } glEnableClientState(GL_COLOR_ARRAY); glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(VoxelShaderVBOData), BUFFER_OFFSET(4*sizeof(float)));//The starting point of colors glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboVoxelsIndicesID); glDrawElements(GL_POINTS, _voxelsInReadArrays, GL_UNSIGNED_INT, BUFFER_OFFSET(0)); //The starting point of the IBO // deactivate vertex and color arrays after drawing glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); // bind with 0 to switch back to normal operation glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); if (!_voxelsAsPoints) { Application::getInstance()->getVoxelShader().end(); glDisableVertexAttribArray(attributeLocation); } else { Application::getInstance()->getPointShader().end(); glDisableVertexAttribArray(attributeLocation); glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); } } else if (!_usePrimitiveRenderer) { PerformanceWarning warn(showWarnings, "render().. TRIANGLES..."); { PerformanceWarning warn(showWarnings,"render().. setup before glDrawRangeElementsEXT()..."); // tell OpenGL where to find vertex and color information glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); glVertexPointer(3, GL_FLOAT, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0); applyScaleAndBindProgram(texture); // for performance, enable backface culling glEnable(GL_CULL_FACE); } // draw voxels in 6 passes { PerformanceWarning warn(showWarnings, "render().. glDrawRangeElementsEXT()..."); glNormal3f(0,1.0f,0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesTop); glDrawRangeElementsEXT(GL_TRIANGLES, 0, GLOBAL_NORMALS_VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, INDICES_PER_FACE * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); glNormal3f(0,-1.0f,0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesBottom); glDrawRangeElementsEXT(GL_TRIANGLES, 0, GLOBAL_NORMALS_VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, INDICES_PER_FACE * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); glNormal3f(-1.0f,0,0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesLeft); glDrawRangeElementsEXT(GL_TRIANGLES, 0, GLOBAL_NORMALS_VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, INDICES_PER_FACE * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); glNormal3f(1.0f,0,0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesRight); glDrawRangeElementsEXT(GL_TRIANGLES, 0, GLOBAL_NORMALS_VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, INDICES_PER_FACE * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); glNormal3f(0,0,-1.0f); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesFront); glDrawRangeElementsEXT(GL_TRIANGLES, 0, GLOBAL_NORMALS_VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, INDICES_PER_FACE * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); glNormal3f(0,0,1.0f); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesBack); glDrawRangeElementsEXT(GL_TRIANGLES, 0, GLOBAL_NORMALS_VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, INDICES_PER_FACE * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); } { PerformanceWarning warn(showWarnings, "render().. cleanup after glDrawRangeElementsEXT()..."); glDisable(GL_CULL_FACE); removeScaleAndReleaseProgram(texture); // deactivate vertex and color arrays after drawing glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); // bind with 0 to switch back to normal operation glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } } else { applyScaleAndBindProgram(texture); _renderer->render(); removeScaleAndReleaseProgram(texture); } } void VoxelSystem::applyScaleAndBindProgram(bool texture) { if (Menu::getInstance()->isOptionChecked(MenuOption::Shadows)) { _shadowMapProgram.bind(); glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID()); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glEnable(GL_TEXTURE_2D); glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[0]); glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[1]); glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[2]); } else if (texture) { _perlinModulateProgram.bind(); glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPermutationNormalTextureID()); } glPushMatrix(); glScalef(_treeScale, _treeScale, _treeScale); } void VoxelSystem::removeScaleAndReleaseProgram(bool texture) { // scale back down to 1 so heads aren't massive glPopMatrix(); if (Menu::getInstance()->isOptionChecked(MenuOption::Shadows)) { _shadowMapProgram.release(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glDisable(GL_TEXTURE_2D); } else if (texture) { _perlinModulateProgram.release(); glBindTexture(GL_TEXTURE_2D, 0); } } int VoxelSystem::_nodeCount = 0; void VoxelSystem::killLocalVoxels() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "VoxelSystem::killLocalVoxels()"); _tree->lockForWrite(); _tree->eraseAllOctreeElements(); _tree->unlock(); clearFreeBufferIndexes(); if (_usePrimitiveRenderer) { if (_renderer) { _renderer->release(); } clearAllNodesBufferIndex(); } _voxelsInReadArrays = 0; // do we need to do this? setupNewVoxelsForDrawing(); } // only called on main thread bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) { _nodeCount++; VoxelTreeElement* voxel = (VoxelTreeElement*)element; voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); 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); } } bool VoxelSystem::inspectForInteriorOcclusionsOperation(OctreeElement* element, void* extraData) { _nodeCount++; VoxelTreeElement* voxel = (VoxelTreeElement*)element; // Nothing to do at the leaf level if (voxel->isLeaf()) { return false; } // Bit mask of occluded shared faces indexed by child unsigned char occludedSharedFace[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // Traverse all pair combinations of children for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) { VoxelTreeElement* childA = voxel->getChildAtIndex(i); if (childA) { // Get the child A's occluding faces, for a leaf that will be // all six voxel faces, and for a non leaf, that will be // all faces which are completely covered by four child octants. unsigned char exteriorOcclusionsA = childA->getExteriorOcclusions(); for (int j = i; --j >= 0; ) { VoxelTreeElement* childB = voxel->getChildAtIndex(j); if (childB) { // Get child B's occluding faces unsigned char exteriorOcclusionsB = childB->getExteriorOcclusions(); // Determine the shared halfspace partition between siblings A and B, // i.e., near/far, left/right, or top/bottom unsigned char partitionA = _sOctantIndexToSharedBitMask[i][j] & exteriorOcclusionsA; unsigned char partitionB = _sOctantIndexToSharedBitMask[i][j] & exteriorOcclusionsB; // Determine which face of each sibling is occluded. // The _sOctantIndexToBitMask is a partition occupancy mask. For // example, if the near-left-top (NLT) and near-left-bottom (NLB) child voxels // exist, the shared partition is top-bottom (TB), and thus the occluded // shared face of the NLT voxel is its bottom face. occludedSharedFace[i] |= (partitionB & _sOctantIndexToBitMask[i]); occludedSharedFace[j] |= (partitionA & _sOctantIndexToBitMask[j]); } } // Exchange bit pairs, left to right, vice versa, etc. occludedSharedFace[i] = _sSwizzledOcclusionBits[occludedSharedFace[i]]; // Combine this voxel's interior excluded shared face only to those children which are coincident // with the excluded face. occludedSharedFace[i] |= (voxel->getInteriorOcclusions() & _sOctantIndexToBitMask[i]); // Inform the child childA->setInteriorOcclusions(occludedSharedFace[i]); if (occludedSharedFace[i] != OctreeElement::HalfSpace::None) { //const glm::vec3& v = voxel->getCorner(); //float s = voxel->getScale(); //qDebug("Child %d of voxel at %f %f %f size: %f has %02x occlusions", i, v.x, v.y, v.z, s, occludedSharedFace[i]); } } } return true; } bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element, void* extraData) { _nodeCount++; VoxelTreeElement* voxel = (VoxelTreeElement*)element; // Nothing to do at the leaf level if (voxel->isLeaf()) { // By definition the the exterior faces of a leaf voxel are // always occluders. voxel->setExteriorOcclusions(OctreeElement::HalfSpace::All); // And the sibling occluders voxel->setInteriorOcclusions(OctreeElement::HalfSpace::None); return false; } else { voxel->setExteriorOcclusions(OctreeElement::HalfSpace::None); voxel->setInteriorOcclusions(OctreeElement::HalfSpace::None); } // Count of exterior occluding faces of this voxel element indexed // by half space partition unsigned int exteriorOcclusionsCt[6] = { 0, 0, 0, 0, 0, 0 }; // Traverse all children for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) { VoxelTreeElement* child = voxel->getChildAtIndex(i); if (child) { // Get the child's occluding faces, for a leaf, that will be // all six voxel faces, and for a non leaf, that will be // all faces which are completely covered by four child octants. unsigned char exteriorOcclusionsOfChild = child->getExteriorOcclusions(); exteriorOcclusionsOfChild &= _sOctantIndexToBitMask[i]; for (int j = 6; --j >= 0; ) { // Determine if the halfspace partition indexed by 1 << j is // present in the exterior occlusions of the child. unsigned char partition = exteriorOcclusionsOfChild & (1 << j); if (partition) { exteriorOcclusionsCt[j]++; } } } } { // Derive the exterior occlusions of the voxel elements from the exclusions // of its children unsigned char exteriorOcclusions = OctreeElement::HalfSpace::None; for (int i = 6; --i >= 0; ) { if (exteriorOcclusionsCt[i] == _sNumOctantsPerHemiVoxel) { // Exactly four octants qualify for full exterior occlusion exteriorOcclusions |= (1 << i); } } // Inform the voxel element voxel->setExteriorOcclusions(exteriorOcclusions); if (exteriorOcclusions == OctreeElement::HalfSpace::All) { //const glm::vec3& v = voxel->getCorner(); //float s = voxel->getScale(); //qDebug("Completely occupied voxel at %f %f %f size: %f", v.x, v.y, v.z, s); // TODO: All of the exterior faces of this voxel element are // occluders, which means that this element is completely // occupied. Hence, the subtree from this node could be // pruned and replaced by a leaf voxel, if the visible // properties of the children are the same } else if (exteriorOcclusions != OctreeElement::HalfSpace::None) { //const glm::vec3& v = voxel->getCorner(); //float s = voxel->getScale(); //qDebug("Partially occupied voxel at %f %f %f size: %f with %02x", v.x, v.y, v.z, s, exteriorOcclusions); } } return true; } void VoxelSystem::cullSharedFaces() { if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) { _useVoxelShader = false; _usePrimitiveRenderer = true; inspectForOcclusions(); } else { _usePrimitiveRenderer = false; clearAllNodesBufferIndex(); } _writeRenderFullVBO = true; _tree->setDirtyBit(); setupNewVoxelsForDrawing(); } void VoxelSystem::showCulledSharedFaces() { _tree->lockForRead(); if (Menu::getInstance()->isOptionChecked(MenuOption::ShowCulledSharedFaces)) { _showCulledSharedFaces = true; } else { _showCulledSharedFaces = false; } _tree->unlock(); if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) { _writeRenderFullVBO = true; _tree->setDirtyBit(); setupNewVoxelsForDrawing(); } } void VoxelSystem::inspectForOcclusions() { if (_inOcclusions) { return; } _inOcclusions = true; _nodeCount = 0; bool showDebugDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showDebugDetails, "inspectForOcclusions()"); _tree->lockForRead(); _tree->recurseTreeWithPostOperation(inspectForExteriorOcclusionsOperation); _nodeCount = 0; _tree->recurseTreeWithOperation(inspectForInteriorOcclusionsOperation); _tree->unlock(); if (showDebugDetails) { qDebug("inspecting all occlusions of %d nodes", _nodeCount); } _inOcclusions = false; } bool VoxelSystem::forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData) { _nodeCount++; element->setDirtyBit(); return true; } void VoxelSystem::forceRedrawEntireTree() { _nodeCount = 0; _tree->recurseTreeWithOperation(forceRedrawEntireTreeOperation); qDebug("forcing redraw of %d nodes", _nodeCount); _tree->setDirtyBit(); setupNewVoxelsForDrawing(); } bool VoxelSystem::isViewChanging() { bool result = false; // assume the best // If our viewFrustum has changed since our _lastKnownViewFrustum if (!_lastKnownViewFrustum.isVerySimilar(_viewFrustum)) { result = true; _lastKnownViewFrustum = *_viewFrustum; // save last known } return result; } bool VoxelSystem::hasViewChanged() { bool result = false; // assume the best // If we're still changing, report no change yet. if (isViewChanging()) { return false; } // If our viewFrustum has changed since our _lastKnownViewFrustum if (!_lastStableViewFrustum.isVerySimilar(_viewFrustum)) { result = true; _lastStableViewFrustum = *_viewFrustum; // save last stable } return result; } // combines the removeOutOfView args into a single class class hideOutOfViewArgs { public: VoxelSystem* thisVoxelSystem; VoxelTree* tree; ViewFrustum thisViewFrustum; ViewFrustum lastViewFrustum; bool culledOnce; bool wantDeltaFrustums; unsigned long nodesScanned; unsigned long nodesRemoved; unsigned long nodesInside; unsigned long nodesIntersect; unsigned long nodesOutside; unsigned long nodesInsideInside; unsigned long nodesIntersectInside; unsigned long nodesOutsideInside; unsigned long nodesInsideOutside; unsigned long nodesOutsideOutside; unsigned long nodesShown; hideOutOfViewArgs(VoxelSystem* voxelSystem, VoxelTree* tree, bool culledOnce, bool widenViewFrustum, bool wantDeltaFrustums) : thisVoxelSystem(voxelSystem), tree(tree), thisViewFrustum(*voxelSystem->getViewFrustum()), lastViewFrustum(*voxelSystem->getLastCulledViewFrustum()), culledOnce(culledOnce), wantDeltaFrustums(wantDeltaFrustums), nodesScanned(0), nodesRemoved(0), nodesInside(0), nodesIntersect(0), nodesOutside(0), nodesInsideInside(0), nodesIntersectInside(0), nodesOutsideInside(0), nodesInsideOutside(0), nodesOutsideOutside(0), nodesShown(0) { // Widen the FOV for trimming if (widenViewFrustum) { float originalFOV = thisViewFrustum.getFieldOfView(); float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; thisViewFrustum.setFieldOfView(wideFOV); thisViewFrustum.calculate(); } } }; void VoxelSystem::hideOutOfView(bool forceFullFrustum) { // don't re-enter... if (_inhideOutOfView) { return; } _inhideOutOfView = true; bool showDebugDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showDebugDetails, "hideOutOfView()"); bool widenFrustum = true; // When using "delta" view frustums and only hide/show items that are in the difference // between the two view frustums. There are some potential problems with this mode. // // 1) This work well for rotating, but what about moving forward? // In the move forward case, you'll get new voxel details, but those // new voxels will be in the last view. // // 2) Also, voxels will arrive from the network that are OUTSIDE of the view // frustum... these won't get hidden... and so we can't assume they are correctly // hidden... // // Both these problems are solved by intermittently calling this with forceFullFrustum set // to true. This will essentially clean up the improperly hidden or shown voxels. // bool wantDeltaFrustums = !forceFullFrustum; hideOutOfViewArgs args(this, this->_tree, _culledOnce, widenFrustum, wantDeltaFrustums); const bool wantViewFrustumDebugging = false; // change to true for additional debugging if (wantViewFrustumDebugging) { args.thisViewFrustum.printDebugDetails(); if (_culledOnce) { args.lastViewFrustum.printDebugDetails(); } } if (!forceFullFrustum && _culledOnce && args.lastViewFrustum.isVerySimilar(args.thisViewFrustum)) { _inhideOutOfView = false; return; } { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "VoxelSystem::... recurseTreeWithOperation(hideOutOfViewOperation)"); _tree->lockForRead(); _tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args); _tree->unlock(); } _lastCulledViewFrustum = args.thisViewFrustum; // save last stable _culledOnce = true; if (args.nodesRemoved) { _tree->setDirtyBit(); setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY); } bool extraDebugDetails = false; // Application::getInstance()->getLogger()->extraDebugging(); if (extraDebugDetails) { qDebug("hideOutOfView() scanned=%ld removed=%ld show=%ld inside=%ld intersect=%ld outside=%ld", args.nodesScanned, args.nodesRemoved, args.nodesShown, args.nodesInside, args.nodesIntersect, args.nodesOutside ); qDebug("inside/inside=%ld intersect/inside=%ld outside/outside=%ld", args.nodesInsideInside, args.nodesIntersectInside, args.nodesOutsideOutside ); qDebug() << "args.thisViewFrustum...."; args.thisViewFrustum.printDebugDetails(); } _inhideOutOfView = false; } bool VoxelSystem::hideAllSubTreeOperation(OctreeElement* element, void* extraData) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; // If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine // how to proceed. If we've never culled, then we just consider all these voxels to be UNKNOWN so that we will not // consider that case. ViewFrustum::location inLastCulledFrustum; if (args->culledOnce && args->wantDeltaFrustums) { inLastCulledFrustum = voxel->inFrustum(args->lastViewFrustum); // if this node is fully OUTSIDE our last culled view frustum, then we don't need to recurse further if (inLastCulledFrustum == ViewFrustum::OUTSIDE) { args->nodesOutsideOutside++; return false; } } args->nodesOutside++; if (voxel->isKnownBufferIndex()) { args->nodesRemoved++; VoxelSystem* thisVoxelSystem = args->thisVoxelSystem; thisVoxelSystem->_voxelsUpdated += thisVoxelSystem->forceRemoveNodeFromArrays(voxel); thisVoxelSystem->setupNewVoxelsForDrawingSingleNode(); } return true; } bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraData) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; // If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine // how to proceed. If we've never culled, then we just consider all these voxels to be UNKNOWN so that we will not // consider that case. ViewFrustum::location inLastCulledFrustum; if (args->culledOnce && args->wantDeltaFrustums) { inLastCulledFrustum = voxel->inFrustum(args->lastViewFrustum); // if this node is fully inside our last culled view frustum, then we don't need to recurse further if (inLastCulledFrustum == ViewFrustum::INSIDE) { args->nodesInsideInside++; return false; } } args->nodesInside++; float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust); voxel->setShouldRender(shouldRender); if (shouldRender && !voxel->isKnownBufferIndex()) { // These are both needed to force redraw... voxel->setDirtyBit(); voxel->markWithChangedTime(); args->nodesShown++; } return true; // keep recursing! } // "hide" voxels in the VBOs that are still in the tree that but not in view. // We don't remove them from the tree, we don't delete them, we do remove them // from the VBOs and mark them as such in the tree. bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; // If we're still recursing the tree using this operator, then we don't know if we're inside or outside... // so before we move forward we need to determine our frustum location ViewFrustum::location inFrustum = voxel->inFrustum(args->thisViewFrustum); // If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine // how to proceed. If we've never culled, then we just consider all these voxels to be UNKNOWN so that we will not // consider that case. ViewFrustum::location inLastCulledFrustum; if (args->culledOnce && args->wantDeltaFrustums) { inLastCulledFrustum = voxel->inFrustum(args->lastViewFrustum); } // ok, now do some processing for this node... switch (inFrustum) { case ViewFrustum::OUTSIDE: { // If this node is outside the current view, then we might want to hide it... unless it was previously OUTSIDE, // if it was previously outside, then we can safely assume it's already hidden, and we can also safely assume // that all of it's children are outside both of our views, in which case we can just stop recursing... if (args->culledOnce && args->wantDeltaFrustums && inLastCulledFrustum == ViewFrustum::OUTSIDE) { args->nodesScanned++; args->nodesOutsideOutside++; return false; // stop recursing this branch! } // if this node is fully OUTSIDE the view, but previously intersected and/or was inside the last view, then // we need to hide it. Additionally we know that ALL of it's children are also fully OUTSIDE so we can recurse // the children and simply mark them as hidden args->tree->recurseNodeWithOperation(voxel, hideAllSubTreeOperation, args ); return false; } break; case ViewFrustum::INSIDE: { // If this node is INSIDE the current view, then we might want to show it... unless it was previously INSIDE, // if it was previously INSIDE, then we can safely assume it's already shown, and we can also safely assume // that all of it's children are INSIDE both of our views, in which case we can just stop recursing... if (args->culledOnce && args->wantDeltaFrustums && inLastCulledFrustum == ViewFrustum::INSIDE) { args->nodesScanned++; args->nodesInsideInside++; return false; // stop recursing this branch! } // if this node is fully INSIDE the view, but previously INTERSECTED and/or was OUTSIDE the last view, then // we need to show it. Additionally we know that ALL of it's children are also fully INSIDE so we can recurse // the children and simply mark them as visible (as appropriate based on LOD) args->tree->recurseNodeWithOperation(voxel, showAllSubTreeOperation, args); return false; } break; case ViewFrustum::INTERSECT: { args->nodesScanned++; // If this node INTERSECTS the current view, then we might want to show it... unless it was previously INSIDE // the last known view, in which case it will already be visible, and we know that all it's children are also // previously INSIDE and visible. So in this case stop recursing if (args->culledOnce && args->wantDeltaFrustums && inLastCulledFrustum == ViewFrustum::INSIDE) { args->nodesIntersectInside++; return false; // stop recursing this branch! } args->nodesIntersect++; // if the child node INTERSECTs the view, then we want to check to see if it thinks it should render // if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from // here because we know will block any children anyway float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust); voxel->setShouldRender(shouldRender); if (voxel->getShouldRender() && !voxel->isKnownBufferIndex()) { voxel->setDirtyBit(); // will this make it draw? voxel->markWithChangedTime(); // both are needed to force redraw args->nodesShown++; return false; } // If it INTERSECTS but shouldn't be displayed, then it's probably a parent and it is at least partially in view. // So we DO want to recurse the children because some of them may not be in view... nothing specifically to do, // just keep iterating the children return true; } break; } // switch return true; // keep going! } void VoxelSystem::nodeAdded(SharedNodePointer node) { if (node->getType() == NodeType::VoxelServer) { qDebug("VoxelSystem... voxel server %s added...", node->getUUID().toString().toLocal8Bit().constData()); _voxelServerCount++; } } bool VoxelSystem::killSourceVoxelsOperation(OctreeElement* element, void* extraData) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; QUuid killedNodeID = *(QUuid*)extraData; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelTreeElement* childNode = voxel->getChildAtIndex(i); if (childNode) { if (childNode->matchesSourceUUID(killedNodeID)) { voxel->safeDeepDeleteChildAtIndex(i); } } } return true; } void VoxelSystem::nodeKilled(SharedNodePointer node) { if (node->getType() == NodeType::VoxelServer) { _voxelServerCount--; QUuid nodeUUID = node->getUUID(); qDebug("VoxelSystem... voxel server %s removed...", nodeUUID.toString().toLocal8Bit().constData()); } } unsigned long VoxelSystem::getFreeMemoryGPU() { // We can't ask all GPUs how much memory they have in use, but we can ask them about how much is free. // So, we can record the free memory before we create our VBOs and the free memory after, and get a basic // idea how how much we're using. _hasMemoryUsageGPU = false; // assume the worst unsigned long freeMemory = 0; const int NUM_RESULTS = 4; // see notes, these APIs return up to 4 results GLint results[NUM_RESULTS] = { 0, 0, 0, 0}; // ATI // http://www.opengl.org/registry/specs/ATI/meminfo.txt // // TEXTURE_FREE_MEMORY_ATI 0x87FC // RENDERBUFFER_FREE_MEMORY_ATI 0x87FD const GLenum VBO_FREE_MEMORY_ATI = 0x87FB; glGetIntegerv(VBO_FREE_MEMORY_ATI, &results[0]); GLenum errorATI = glGetError(); if (errorATI == GL_NO_ERROR) { _hasMemoryUsageGPU = true; freeMemory = results[0]; } else { // NVIDIA // http://developer.download.nvidia.com/opengl/specs/GL_NVX_gpu_memory_info.txt // //const GLenum GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX = 0x9047; //const GLenum GPU_MEMORY_INFO_EVICTION_COUNT_NVX = 0x904A; //const GLenum GPU_MEMORY_INFO_EVICTED_MEMORY_NVX = 0x904B; //const GLenum GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX = 0x9048; const GLenum GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX = 0x9049; results[0] = 0; glGetIntegerv(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &results[0]); freeMemory += results[0]; GLenum errorNVIDIA = glGetError(); if (errorNVIDIA == GL_NO_ERROR) { _hasMemoryUsageGPU = true; freeMemory = results[0]; } } const unsigned long BYTES_PER_KBYTE = 1024; return freeMemory * BYTES_PER_KBYTE; // API results in KB, we want it in bytes } unsigned long VoxelSystem::getVoxelMemoryUsageGPU() { unsigned long currentFreeMemory = getFreeMemoryGPU(); return (_initialMemoryUsageGPU - currentFreeMemory); } // Swizzle value of bit pairs of the value of index unsigned short VoxelSystem::_sSwizzledOcclusionBits[64] = { 0x0000, // 00000000 0x0002, // 00000001 0x0001, // 00000010 0x0003, // 00000011 0x0008, // 00000100 0x000a, // 00000101 0x0009, // 00000110 0x000b, // 00000111 0x0004, // 00001000 0x0006, // 00001001 0x0005, // 00001010 0x0007, // 00001011 0x000c, // 00001100 0x000e, // 00001101 0x000d, // 00001110 0x000f, // 00001111 0x0020, // 00010000 0x0022, // 00010001 0x0021, // 00010010 0x0023, // 00010011 0x0028, // 00010100 0x002a, // 00010101 0x0029, // 00010110 0x002b, // 00010111 0x0024, // 00011000 0x0026, // 00011001 0x0025, // 00011010 0x0027, // 00011011 0x002c, // 00011100 0x002e, // 00011101 0x002d, // 00011110 0x002f, // 00011111 0x0010, // 00100000 0x0012, // 00100001 0x0011, // 00100010 0x0013, // 00100011 0x0018, // 00100100 0x001a, // 00100101 0x0019, // 00100110 0x001b, // 00100111 0x0014, // 00101000 0x0016, // 00101001 0x0015, // 00101010 0x0017, // 00101011 0x001c, // 00101100 0x001e, // 00101101 0x001d, // 00101110 0x001f, // 00101111 0x0030, // 00110000 0x0032, // 00110001 0x0031, // 00110010 0x0033, // 00110011 0x0038, // 00110100 0x003a, // 00110101 0x0039, // 00110110 0x003b, // 00110111 0x0034, // 00111000 0x0036, // 00111001 0x0035, // 00111010 0x0037, // 00111011 0x003c, // 00111100 0x003e, // 00111101 0x003d, // 00111110 0x003f, // 00111111 }; // Octant bitmask array indexed by octant. The mask value indicates the octant's halfspace partitioning. The index // value corresponds to the voxel's octal code derived in "pointToVoxel" in SharedUtil.cpp, which, BTW, does *not* // correspond to the "ChildIndex" enum value in OctreeElement.h unsigned char VoxelSystem::_sOctantIndexToBitMask[8] = { OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near, OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far, OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near, OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far, OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near, OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far, OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near, OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far, }; // Two dimensional array map indexed by octant row and column. The mask value // indicates the two faces shared by the octants unsigned char VoxelSystem::_sOctantIndexToSharedBitMask[8][8] = { { // Index 0: Bottom-Left-Near 0, // Bottom-Left-Near OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Far OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Near 0, // Top-Left-Far OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Near 0, // Bottom-Right-Far 0, // Top-Right-Near 0, // Top-Right-Far }, { // Index 1: Bottom-Left-Far OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Near 0, // Bottom-Left-Far 0, // Top-Left-Near OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Far 0, // Bottom-Right-Near OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Far 0, // Top-Right-Near 0, // Top-Right-Far }, { // Index 2: Top-Left-Near OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Near 0, // Bottom-Left-Far 0, // Top-Left-Near OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Far 0, // Bottom-Right-Near 0, // Bottom-Right-Far OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Near 0, // Top-Right-Far }, { // Index 3: Top-Left-Far 0, // Bottom-Left-Near OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Far OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Near 0, // Top-Left-Far 0, // Bottom-Right-Near 0, // Bottom-Right-Far 0, // Top-Right-Near OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Far }, { // Index 4: Bottom-Right-Near OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Near 0, // Bottom-Left-Far 0, // Top-Left-Near 0, // Top-Left-Far 0, // Bottom-Right-Near OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Far OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Near 0, // Top-Right-Far }, { // Index 5: Bottom-Right-Far 0, // Bottom-Left-Near OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Far 0, // Top-Left-Near 0, // Top-Left-Far OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Near 0, // Bottom-Right-Far 0, // Top-Right-Near OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Far }, { // Index 6: Top-Right-Near 0, // Bottom-Left-Near 0, // Bottom-Left-Far OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Near 0, // Top-Left-Far OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Near 0, // Bottom-Right-Far 0, // Top-Right-Near OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Far }, { // Index 7: Top-Right-Far 0, // Bottom-Left-Near 0, // Bottom-Left-Far 0, // Top-Left-Near OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Far 0, // Bottom-Right-Near OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Far OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Near 0, // Top-Right-Far }, };