//// Cube.cpp// interface//// Created by Philip on 12/31/12.// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.// #ifdef _WIN32#define _timeval_#define _USE_MATH_DEFINES#endif #include <cstring>#include <cmath>#include <iostream> // to load voxels from file#include <fstream> // to load voxels from file#include <pthread.h> #include <glm/gtc/random.hpp> #include <OctalCode.h>#include <PacketHeaders.h>#include <PerfStat.h>#include <SharedUtil.h>#include <NodeList.h>#include <NodeTypes.h> #include "Application.h"#include "CoverageMap.h"#include "CoverageMapV2.h"#include "InterfaceConfig.h"#include "renderer/ProgramObject.h"#include "VoxelConstants.h"#include "VoxelSystem.h" 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,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1, 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1 }; 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+ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) : NodeData(NULL), _treeScale(treeScale), _maxVoxels(maxVoxels) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; _writeRenderFullVBO = true; _readRenderFullVBO = true; _tree = new VoxelTree(); pthread_mutex_init(&_bufferWriteLock, NULL); pthread_mutex_init(&_treeLock, NULL); VoxelNode::addDeleteHook(this); _abandonedVBOSlots = 0; _falseColorizeBySource = false; _dataSourceID = UNKNOWN_NODE_ID;} void VoxelSystem::nodeDeleted(VoxelNode* node) { if (node->isKnownBufferIndex() && (node->getVoxelSystem() == this)) { freeBufferIndex(node->getBufferIndex()); }} void VoxelSystem::freeBufferIndex(glBufferIndex index) { _freeIndexes.push_back(index);} void VoxelSystem::clearFreeBufferIndexes() { for (int i = 0; i < _freeIndexes.size(); i++) { glBufferIndex nodeIndex = _freeIndexes[i]; glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX); float voxelScale = 0; _writeVoxelDirtyArray[nodeIndex] = true; nodeColor color = {0, 0, 0, 0}; updateNodeInArrays(nodeIndex, startVertex, voxelScale, color); _abandonedVBOSlots++; } _freeIndexes.clear();} VoxelSystem::~VoxelSystem() { delete[] _readVerticesArray; delete[] _writeVerticesArray; delete[] _readColorsArray; delete[] _writeColorsArray; delete[] _writeVoxelDirtyArray; delete[] _readVoxelDirtyArray; delete _tree; pthread_mutex_destroy(&_bufferWriteLock); pthread_mutex_destroy(&_treeLock); VoxelNode::removeDeleteHook(this);} void VoxelSystem::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { _tree->loadVoxelsFile(fileName, wantColorRandomizer); setupNewVoxelsForDrawing();} void VoxelSystem::writeToSVOFile(const char* filename, VoxelNode* node) const { _tree->writeToSVOFile(filename, node);} bool VoxelSystem::readFromSVOFile(const char* filename) { bool result = _tree->readFromSVOFile(filename); if (result) { setupNewVoxelsForDrawing(); } return result;} long int VoxelSystem::getVoxelsCreated() { return _tree->voxelsCreated;} float VoxelSystem::getVoxelsCreatedPerSecondAverage() { return (1 / _tree->voxelsCreatedStats.getEventDeltaAverage());} long int VoxelSystem::getVoxelsColored() { return _tree->voxelsColored;} float VoxelSystem::getVoxelsColoredPerSecondAverage() { return (1 / _tree->voxelsColoredStats.getEventDeltaAverage());} long int VoxelSystem::getVoxelsBytesRead() { return _tree->voxelsBytesRead;} float VoxelSystem::getVoxelsBytesReadPerSecondAverage() { return _tree->voxelsBytesReadStats.getAverageSampleValuePerSecond();} int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { unsigned char command = *sourceBuffer; int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); unsigned char* voxelData = sourceBuffer + numBytesPacketHeader; pthread_mutex_lock(&_treeLock); switch(command) { case PACKET_TYPE_VOXEL_DATA: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceID()); _tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, args); } break; case PACKET_TYPE_VOXEL_DATA_MONOCHROME: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // ask the VoxelTree to read the MONOCHROME bitstream into the tree ReadBitstreamToTreeParams args(NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceID()); _tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, args); } break; case PACKET_TYPE_Z_COMMAND: // the Z command is a special command that allows the sender to send high level semantic // requests, like erase all, or add sphere scene, different receivers may handle these // messages differently char* packetData = (char *)sourceBuffer; char* command = &packetData[numBytesPacketHeader]; // start of the command int commandLength = strlen(command); // commands are null terminated strings int totalLength = 1+commandLength+1; qDebug("got Z message len(%d)= %s\n", numBytes, command); while (totalLength <= numBytes) { if (0==strcmp(command,(char*)"erase all")) { qDebug("got Z message == erase all\n"); _tree->eraseAllVoxels(); _voxelsInReadArrays = _voxelsInWriteArrays = 0; // better way to do this?? } if (0==strcmp(command,(char*)"add scene")) { qDebug("got Z message == add scene - NOT SUPPORTED ON INTERFACE\n"); } totalLength += commandLength+1; } break; } setupNewVoxelsForDrawing(); pthread_mutex_unlock(&_treeLock); Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(numBytes); return numBytes;} void VoxelSystem::setupNewVoxelsForDrawing() { PerformanceWarning warn(_renderWarningsOn, "setupNewVoxelsForDrawing()"); // would like to include _voxelsInArrays, _voxelsUpdated uint64_t start = usecTimestampNow(); uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; // clear up the VBOs for any nodes that have been recently deleted. clearFreeBufferIndexes(); 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 } uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000; // If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view if ((sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) && !isViewChanging()) { _lastViewCulling = start; // When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove // them from tree, this makes our tree caclulations faster, but doesn't require us to fully rebuild the VBOs (which // can be expensive). removeOutOfView(); // 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(); uint64_t endViewCulling = usecTimestampNow(); _lastViewCullingElapsed = (endViewCulling - start) / 1000; } bool didWriteFullVBO = _writeRenderFullVBO; if (_tree->isDirty()) { static char buffer[64] = { 0 }; if (_renderWarningsOn) { sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", debug::valueOf(_writeRenderFullVBO)); }; PerformanceWarning warn(_renderWarningsOn, buffer); _callsToTreesToArrays++; if (_writeRenderFullVBO) { _voxelsInWriteArrays = 0; // reset our VBO } _voxelsUpdated = newTreeToArrays(_tree->rootNode); _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 } // since we called treeToArrays, we can assume that our VBO is in sync, and so partial updates to the VBOs are // ok again, until/unless we call removeOutOfView() _writeRenderFullVBO = false; } else { _voxelsUpdated = 0; } // lock on the buffer write lock so we can't modify the data when the GPU is reading it pthread_mutex_lock(&_bufferWriteLock); if (_voxelsUpdated) { _voxelsDirty=true; } // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated copyWrittenDataToReadArrays(didWriteFullVBO); pthread_mutex_unlock(&_bufferWriteLock); uint64_t end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; _setupNewVoxelsForDrawingLastElapsed = elapsedmsec;} void VoxelSystem::cleanupRemovedVoxels() { PerformanceWarning warn(_renderWarningsOn, "cleanupRemovedVoxels()"); // This handles cleanup of voxels that were culled as part of our regular out of view culling operation if (!_removedVoxels.isEmpty()) { 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.25f; if (!_writeRenderFullVBO && (_abandonedVBOSlots > (_voxelsInWriteArrays * 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; GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes); segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); memcpy(readColorsAt, writeColorsAt, segmentSizeBytes);} void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { PerformanceWarning warn(_renderWarningsOn, "copyWrittenDataToReadArrays()"); if (_voxelsDirty && _voxelsUpdated) { if (fullVBOs) { copyWrittenDataToReadArraysFullVBOs(); } else { copyWrittenDataToReadArraysPartialVBOs(); } }} int VoxelSystem::newTreeToArrays(VoxelNode* node) { int voxelsUpdated = 0; bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! shouldRender = node->calculateShouldRender(Application::getInstance()->getViewFrustum()); node->setShouldRender(shouldRender); // let children figure out their renderness if (!node->isLeaf()) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { if (node->getChildAtIndex(i)) { voxelsUpdated += newTreeToArrays(node->getChildAtIndex(i)); } } } if (_writeRenderFullVBO) { voxelsUpdated += updateNodeInArraysAsFullVBO(node); } else { voxelsUpdated += updateNodeInArraysAsPartialVBO(node); } node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. return voxelsUpdated;} int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } if (node->getShouldRender()) { glm::vec3 startVertex = node->getCorner(); float voxelScale = node->getScale(); glBufferIndex nodeIndex = _voxelsInWriteArrays; // populate the array with points for the 8 vertices // and RGB color for each added vertex updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); node->setBufferIndex(nodeIndex); node->setVoxelSystem(this); _writeVoxelDirtyArray[nodeIndex] = true; // just in case we switch to Partial mode _voxelsInWriteArrays++; // our know vertices in the arrays return 1; // rendered } else { node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); node->setVoxelSystem(NULL); } return 0; // not-rendered} int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } // Now, if we've changed any attributes (our renderness, our color, etc) then update the Arrays... if (node->isDirty()) { glm::vec3 startVertex; float voxelScale = 0; // If we're should render, use our legit location and scale, if (node->getShouldRender()) { startVertex = node->getCorner(); voxelScale = node->getScale(); } else { // if we shouldn't render then set out location to some infinitely distant location, // and our scale as infinitely small startVertex[0] = startVertex[1] = startVertex[2] = FLT_MAX; voxelScale = 0; _abandonedVBOSlots++; } // If this node has not yet been written to the array, then add it to the end of the array. glBufferIndex nodeIndex; if (node->isKnownBufferIndex()) { nodeIndex = node->getBufferIndex(); } else { nodeIndex = _voxelsInWriteArrays; node->setBufferIndex(nodeIndex); node->setVoxelSystem(this); _voxelsInWriteArrays++; } _writeVoxelDirtyArray[nodeIndex] = true; // populate the array with points for the 8 vertices // and RGB color for each added vertex updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); return 1; // updated! } return 0; // not-updated} void VoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) { GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); *(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[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 = 0;GLuint VoxelSystem::_permutationNormalTextureID = 0; void VoxelSystem::init() { _renderWarningsOn = false; _callsToTreesToArrays = 0; _setupNewVoxelsForDrawingLastFinished = 0; _setupNewVoxelsForDrawingLastElapsed = 0; _lastViewCullingElapsed = _lastViewCulling = 0; // When we change voxels representations in the arrays, we'll update this _voxelsDirty = false; _voxelsInWriteArrays = 0; _voxelsInReadArrays = 0; // we will track individual dirty sections with these arrays of bools _writeVoxelDirtyArray = new bool[_maxVoxels]; memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); _readVoxelDirtyArray = new bool[_maxVoxels]; memset(_readVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); // prep the data structures for incoming voxel dataLines 1–500 of 1544. Load more
// // Cube.cpp // interface // // Created by Philip on 12/31/12. // Copyright (c) 2012 High Fidelity, Inc. All rights reserved. // #ifdef _WIN32 #define _timeval_ #define _USE_MATH_DEFINES #endif #include <cstring> #include <cmath> #include <iostream> // to load voxels from file #include <fstream> // to load voxels from file #include <pthread.h> #include <glm/gtc/random.hpp> #include <OctalCode.h> #include <PacketHeaders.h> #include <PerfStat.h> #include <SharedUtil.h> #include <NodeList.h> #include <NodeTypes.h> #include "Application.h" #include "CoverageMap.h" #include "CoverageMapV2.h" #include "InterfaceConfig.h" #include "renderer/ProgramObject.h" #include "VoxelConstants.h" #include "VoxelSystem.h" 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,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1, 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1 }; 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+ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) : NodeData(NULL), _treeScale(treeScale), _maxVoxels(maxVoxels) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; _writeRenderFullVBO = true; _readRenderFullVBO = true; _tree = new VoxelTree(); pthread_mutex_init(&_bufferWriteLock, NULL); pthread_mutex_init(&_treeLock, NULL); VoxelNode::addDeleteHook(this); _abandonedVBOSlots = 0; _falseColorizeBySource = false; _dataSourceID = UNKNOWN_NODE_ID; } void VoxelSystem::nodeDeleted(VoxelNode* node) { if (node->isKnownBufferIndex() && (node->getVoxelSystem() == this)) { freeBufferIndex(node->getBufferIndex()); } } void VoxelSystem::freeBufferIndex(glBufferIndex index) { _freeIndexes.push_back(index); } void VoxelSystem::clearFreeBufferIndexes() { for (int i = 0; i < _freeIndexes.size(); i++) { glBufferIndex nodeIndex = _freeIndexes[i]; glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX); float voxelScale = 0; _writeVoxelDirtyArray[nodeIndex] = true; nodeColor color = {0, 0, 0, 0}; updateNodeInArrays(nodeIndex, startVertex, voxelScale, color); _abandonedVBOSlots++; } _freeIndexes.clear(); } VoxelSystem::~VoxelSystem() { delete[] _readVerticesArray; delete[] _writeVerticesArray; delete[] _readColorsArray; delete[] _writeColorsArray; delete[] _writeVoxelDirtyArray; delete[] _readVoxelDirtyArray; delete _tree; pthread_mutex_destroy(&_bufferWriteLock); pthread_mutex_destroy(&_treeLock); VoxelNode::removeDeleteHook(this); } void VoxelSystem::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { _tree->loadVoxelsFile(fileName, wantColorRandomizer); setupNewVoxelsForDrawing(); } void VoxelSystem::writeToSVOFile(const char* filename, VoxelNode* node) const { _tree->writeToSVOFile(filename, node); } bool VoxelSystem::readFromSVOFile(const char* filename) { bool result = _tree->readFromSVOFile(filename); if (result) { setupNewVoxelsForDrawing(); } return result; } long int VoxelSystem::getVoxelsCreated() { return _tree->voxelsCreated; } float VoxelSystem::getVoxelsCreatedPerSecondAverage() { return (1 / _tree->voxelsCreatedStats.getEventDeltaAverage()); } long int VoxelSystem::getVoxelsColored() { return _tree->voxelsColored; } float VoxelSystem::getVoxelsColoredPerSecondAverage() { return (1 / _tree->voxelsColoredStats.getEventDeltaAverage()); } long int VoxelSystem::getVoxelsBytesRead() { return _tree->voxelsBytesRead; } float VoxelSystem::getVoxelsBytesReadPerSecondAverage() { return _tree->voxelsBytesReadStats.getAverageSampleValuePerSecond(); } int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { unsigned char command = *sourceBuffer; int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); unsigned char* voxelData = sourceBuffer + numBytesPacketHeader; pthread_mutex_lock(&_treeLock); switch(command) { case PACKET_TYPE_VOXEL_DATA: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceID()); _tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, args); } break; case PACKET_TYPE_VOXEL_DATA_MONOCHROME: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // ask the VoxelTree to read the MONOCHROME bitstream into the tree ReadBitstreamToTreeParams args(NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceID()); _tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, args); } break; case PACKET_TYPE_Z_COMMAND: // the Z command is a special command that allows the sender to send high level semantic // requests, like erase all, or add sphere scene, different receivers may handle these // messages differently char* packetData = (char *)sourceBuffer; char* command = &packetData[numBytesPacketHeader]; // start of the command int commandLength = strlen(command); // commands are null terminated strings int totalLength = 1+commandLength+1; qDebug("got Z message len(%d)= %s\n", numBytes, command); while (totalLength <= numBytes) { if (0==strcmp(command,(char*)"erase all")) { qDebug("got Z message == erase all\n"); _tree->eraseAllVoxels(); _voxelsInReadArrays = _voxelsInWriteArrays = 0; // better way to do this?? } if (0==strcmp(command,(char*)"add scene")) { qDebug("got Z message == add scene - NOT SUPPORTED ON INTERFACE\n"); } totalLength += commandLength+1; } break; } setupNewVoxelsForDrawing(); pthread_mutex_unlock(&_treeLock); Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(numBytes); return numBytes; } void VoxelSystem::setupNewVoxelsForDrawing() { PerformanceWarning warn(_renderWarningsOn, "setupNewVoxelsForDrawing()"); // would like to include _voxelsInArrays, _voxelsUpdated uint64_t start = usecTimestampNow(); uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; // clear up the VBOs for any nodes that have been recently deleted. clearFreeBufferIndexes(); 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 } uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000; // If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view if ((sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) && !isViewChanging()) { _lastViewCulling = start; // When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove // them from tree, this makes our tree caclulations faster, but doesn't require us to fully rebuild the VBOs (which // can be expensive). removeOutOfView(); // 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(); uint64_t endViewCulling = usecTimestampNow(); _lastViewCullingElapsed = (endViewCulling - start) / 1000; } bool didWriteFullVBO = _writeRenderFullVBO; if (_tree->isDirty()) { static char buffer[64] = { 0 }; if (_renderWarningsOn) { sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", debug::valueOf(_writeRenderFullVBO)); }; PerformanceWarning warn(_renderWarningsOn, buffer); _callsToTreesToArrays++; if (_writeRenderFullVBO) { _voxelsInWriteArrays = 0; // reset our VBO } _voxelsUpdated = newTreeToArrays(_tree->rootNode); _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 } // since we called treeToArrays, we can assume that our VBO is in sync, and so partial updates to the VBOs are // ok again, until/unless we call removeOutOfView() _writeRenderFullVBO = false; } else { _voxelsUpdated = 0; } // lock on the buffer write lock so we can't modify the data when the GPU is reading it pthread_mutex_lock(&_bufferWriteLock); if (_voxelsUpdated) { _voxelsDirty=true; } // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated copyWrittenDataToReadArrays(didWriteFullVBO); pthread_mutex_unlock(&_bufferWriteLock); uint64_t end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; } void VoxelSystem::cleanupRemovedVoxels() { PerformanceWarning warn(_renderWarningsOn, "cleanupRemovedVoxels()"); // This handles cleanup of voxels that were culled as part of our regular out of view culling operation if (!_removedVoxels.isEmpty()) { 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.25f; if (!_writeRenderFullVBO && (_abandonedVBOSlots > (_voxelsInWriteArrays * 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; GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes); segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); memcpy(readColorsAt, writeColorsAt, segmentSizeBytes); } void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { PerformanceWarning warn(_renderWarningsOn, "copyWrittenDataToReadArrays()"); if (_voxelsDirty && _voxelsUpdated) { if (fullVBOs) { copyWrittenDataToReadArraysFullVBOs(); } else { copyWrittenDataToReadArraysPartialVBOs(); } } } int VoxelSystem::newTreeToArrays(VoxelNode* node) { int voxelsUpdated = 0; bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! shouldRender = node->calculateShouldRender(Application::getInstance()->getViewFrustum()); node->setShouldRender(shouldRender); // let children figure out their renderness if (!node->isLeaf()) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { if (node->getChildAtIndex(i)) { voxelsUpdated += newTreeToArrays(node->getChildAtIndex(i)); } } } if (_writeRenderFullVBO) { voxelsUpdated += updateNodeInArraysAsFullVBO(node); } else { voxelsUpdated += updateNodeInArraysAsPartialVBO(node); } node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. return voxelsUpdated; } int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } if (node->getShouldRender()) { glm::vec3 startVertex = node->getCorner(); float voxelScale = node->getScale(); glBufferIndex nodeIndex = _voxelsInWriteArrays; // populate the array with points for the 8 vertices // and RGB color for each added vertex updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); node->setBufferIndex(nodeIndex); node->setVoxelSystem(this); _writeVoxelDirtyArray[nodeIndex] = true; // just in case we switch to Partial mode _voxelsInWriteArrays++; // our know vertices in the arrays return 1; // rendered } else { node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); node->setVoxelSystem(NULL); } return 0; // not-rendered } int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } // Now, if we've changed any attributes (our renderness, our color, etc) then update the Arrays... if (node->isDirty()) { glm::vec3 startVertex; float voxelScale = 0; // If we're should render, use our legit location and scale, if (node->getShouldRender()) { startVertex = node->getCorner(); voxelScale = node->getScale(); } else { // if we shouldn't render then set out location to some infinitely distant location, // and our scale as infinitely small startVertex[0] = startVertex[1] = startVertex[2] = FLT_MAX; voxelScale = 0; _abandonedVBOSlots++; } // If this node has not yet been written to the array, then add it to the end of the array. glBufferIndex nodeIndex; if (node->isKnownBufferIndex()) { nodeIndex = node->getBufferIndex(); } else { nodeIndex = _voxelsInWriteArrays; node->setBufferIndex(nodeIndex); node->setVoxelSystem(this); _voxelsInWriteArrays++; } _writeVoxelDirtyArray[nodeIndex] = true; // populate the array with points for the 8 vertices // and RGB color for each added vertex updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); return 1; // updated! } return 0; // not-updated } void VoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) { GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); *(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[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 = 0; GLuint VoxelSystem::_permutationNormalTextureID = 0; void VoxelSystem::init() { _renderWarningsOn = false; _callsToTreesToArrays = 0; _setupNewVoxelsForDrawingLastFinished = 0; _setupNewVoxelsForDrawingLastElapsed = 0; _lastViewCullingElapsed = _lastViewCulling = 0; // When we change voxels representations in the arrays, we'll update this _voxelsDirty = false; _voxelsInWriteArrays = 0; _voxelsInReadArrays = 0; // we will track individual dirty sections with these arrays of bools _writeVoxelDirtyArray = new bool[_maxVoxels]; memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); _readVoxelDirtyArray = new bool[_maxVoxels]; memset(_readVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); // prep the data structures for incoming voxel data