diff --git a/examples/Basic/main.cpp b/examples/Basic/main.cpp index 5ad96666..431b8d6b 100644 --- a/examples/Basic/main.cpp +++ b/examples/Basic/main.cpp @@ -559,17 +559,17 @@ int main(int argc, char *argv[]) openGLWidget.show(); //Create an empty volume and then place a sphere in it - Volume volData(128); + Volume volData(&load, &unload, 128); //If these two lines don't compile, please try commenting them out and using the two lines after //(you will need Boost for this). If you have to do this then please let us know in the forums as //we rely on community feedback to keep the Boost version running. - volData.dataRequiredHandler = &load; - volData.dataOverflowHandler = &unload; + //volData.dataRequiredHandler = &load; + //volData.dataOverflowHandler = &unload; //volData.dataRequiredHandler = polyvox_bind(&load, polyvox_placeholder_1, polyvox_placeholder_2); //volData.dataOverflowHandler = polyvox_bind(&unload, polyvox_placeholder_1, polyvox_placeholder_2); - volData.setBlockCacheSize(4096); + //volData.setMaxNumberOfUncompressedBlocks(4096); //createSphereInVolume(volData, 30); //createPerlinTerrain(volData); //createPerlinVolumeSlow(volData); diff --git a/examples/OpenGL/OpenGLWidget.h b/examples/OpenGL/OpenGLWidget.h index dd31af7f..21829d06 100644 --- a/examples/OpenGL/OpenGLWidget.h +++ b/examples/OpenGL/OpenGLWidget.h @@ -37,7 +37,7 @@ freely, subject to the following restrictions: #include "OpenGLVertexBufferObjectSupport.h" #include "Shapes.h" -const uint16_t g_uVolumeSideLength = 128; +const int32_t g_uVolumeSideLength = 128; class OpenGLWidget : public QGLWidget { diff --git a/library/PolyVoxCore/include/PolyVoxImpl/Block.h b/library/PolyVoxCore/include/PolyVoxImpl/Block.h index ca166e7c..4f8b6e84 100644 --- a/library/PolyVoxCore/include/PolyVoxImpl/Block.h +++ b/library/PolyVoxCore/include/PolyVoxImpl/Block.h @@ -62,7 +62,7 @@ namespace PolyVox public: void compress(void); - void uncompress(VoxelType* pData); + void uncompress(void); std::vector< RunlengthEntry > m_vecCompressedData; VoxelType* m_tUncompressedData; diff --git a/library/PolyVoxCore/include/PolyVoxImpl/Block.inl b/library/PolyVoxCore/include/PolyVoxImpl/Block.inl index 27bdd064..2ff6ec12 100644 --- a/library/PolyVoxCore/include/PolyVoxImpl/Block.inl +++ b/library/PolyVoxCore/include/PolyVoxImpl/Block.inl @@ -36,7 +36,7 @@ namespace PolyVox Block::Block(uint16_t uSideLength) :m_uSideLength(0) ,m_uSideLengthPower(0) - ,m_tUncompressedData(NULL) + ,m_tUncompressedData(0) ,m_bIsCompressed(true) ,m_bIsUncompressedDataModified(true) { @@ -103,14 +103,17 @@ namespace PolyVox template void Block::fill(VoxelType tValue) { - if(!m_bIsCompressed) { + if(!m_bIsCompressed) + { //The memset *may* be faster than the std::fill(), but it doesn't compile nicely //in 64-bit mode as casting the pointer to an int causes a loss of precision. const uint32_t uNoOfVoxels = m_uSideLength * m_uSideLength * m_uSideLength; std::fill(m_tUncompressedData, m_tUncompressedData + uNoOfVoxels, tValue); m_bIsUncompressedDataModified = true; - } else { + } + else + { RunlengthEntry rle; rle.length = m_uSideLength*m_uSideLength*m_uSideLength; rle.value = tValue; @@ -186,17 +189,18 @@ namespace PolyVox std::vector< RunlengthEntry >(m_vecCompressedData).swap(m_vecCompressedData); } - //Flag the uncompressed data as no longer being used but don't delete it (we don't own it). + //Flag the uncompressed data as no longer being used. + delete[] m_tUncompressedData; m_tUncompressedData = 0; m_bIsCompressed = true; } template - void Block::uncompress(VoxelType* pData) + void Block::uncompress(void) { assert(m_bIsCompressed == true); assert(m_tUncompressedData == 0); - m_tUncompressedData = pData; + m_tUncompressedData = new VoxelType[m_uSideLength * m_uSideLength * m_uSideLength]; VoxelType* pUncompressedData = m_tUncompressedData; for(uint32_t ct = 0; ct < m_vecCompressedData.size(); ++ct) diff --git a/library/PolyVoxCore/include/Volume.h b/library/PolyVoxCore/include/Volume.h index a9f07f18..e182bedd 100644 --- a/library/PolyVoxCore/include/Volume.h +++ b/library/PolyVoxCore/include/Volume.h @@ -121,23 +121,40 @@ namespace PolyVox public: LoadedBlock(uint16_t uSideLength = 0) :block(uSideLength) - ,uncompressedData(0) ,timestamp(0) { } Block block; - VoxelType* uncompressedData; uint32_t timestamp; }; public: /// Constructor - Volume(uint16_t uBlockSideLength = 32); + Volume + ( + polyvox_function&, const Region&)> dataRequiredHandler, + polyvox_function&, const Region&)> dataOverflowHandler, + uint16_t uBlockSideLength = 32 + ); /// Constructor - Volume(const Region& regValid, uint16_t uBlockSideLength = 32); + Volume + ( + const Region& regValid, + polyvox_function&, const Region&)> dataRequiredHandler = 0, + polyvox_function&, const Region&)> dataOverflowHandler = 0, + bool bStreamingEnabled = false, + uint16_t uBlockSideLength = 32 + ); /// Constructor - Volume(int32_t uWidth, int32_t uHeight, int32_t uDepth, uint16_t uBlockSideLength = 32); + Volume + ( + int32_t uWidth, int32_t uHeight, int32_t uDepth, + polyvox_function&, const Region&)> dataRequiredHandler = 0, + polyvox_function&, const Region&)> dataOverflowHandler = 0, + bool bStreamingEnabled = false, + uint16_t uBlockSideLength = 32 + ); /// Destructor ~Volume(); @@ -163,9 +180,9 @@ namespace PolyVox VoxelType getVoxelAt(const Vector3DInt32& v3dPos) const; /// Sets the number of blocks for which uncompressed data is stored. - void setBlockCacheSize(uint16_t uBlockCacheSize); + void setMaxNumberOfUncompressedBlocks(uint16_t uMaxNumberOfUncompressedBlocks); /// Sets the number of blocks which can be in memory before unload is called - void setMaxBlocksLoaded(uint16_t uBlockCacheSize); + void setMaxNumberOfBlocksInMemory(uint16_t uMaxNumberOfBlocksInMemory); /// Sets the value used for voxels which are outside the volume void setBorderValue(const VoxelType& tBorder); /// Sets the voxel at an x,y,z position @@ -179,19 +196,21 @@ namespace PolyVox /// Deprecated - I don't think we should expose this function? Let us know if you disagree... void resize(const Region& regValidRegion, uint16_t uBlockSideLength); + +private: /// gets called when a new region is allocated and needs to be filled /// NOTE: accessing ANY voxels outside this region during the process of this function /// is absolutely unsafe - polyvox_function&, const Region&)> dataRequiredHandler; + polyvox_function&, const Region&)> m_funcDataRequiredHandler; /// gets called when a Region needs to be stored by the user, because Volume will erase it right after /// this function returns /// NOTE: accessing ANY voxels outside this region during the process of this function /// is absolutely unsafe - polyvox_function&, const Region&)> dataOverflowHandler; - private: + polyvox_function&, const Region&)> m_funcDataOverflowHandler; + Block* getUncompressedBlock(int32_t uBlockX, int32_t uBlockY, int32_t uBlockZ) const; void eraseBlock(typename std::map::iterator itBlock) const; - /// this function can be called by dataRequiredHandler without causing any weird effects + /// this function can be called by m_funcDataRequiredHandler without causing any weird effects bool setVoxelAtConst(int32_t uXPos, int32_t uYPos, int32_t uZPos, VoxelType tValue) const; //The block data @@ -204,9 +223,9 @@ namespace PolyVox mutable std::vector< LoadedBlock* > m_vecUncompressedBlockCache; mutable uint32_t m_uTimestamper; mutable Vector3DInt32 m_v3dLastAccessedBlockPos; - mutable LoadedBlock* m_pLastAccessedBlock; - uint32_t m_uMaxUncompressedBlockCacheSize; - uint32_t m_uMaxBlocksLoaded; + mutable Block* m_pLastAccessedBlock; + uint32_t m_uMaxNumberOfUncompressedBlocks; + uint32_t m_uMaxNumberOfBlocksInMemory; //We don't store an actual Block for the border, just the uncompressed data. This is partly because the border //block does not have a position (so can't be passed to getUncompressedBlock()) and partly because there's a @@ -226,6 +245,8 @@ namespace PolyVox int32_t m_uLongestSideLength; int32_t m_uShortestSideLength; float m_fDiagonalLength; + + bool m_bStreamingEnabled; }; } diff --git a/library/PolyVoxCore/include/Volume.inl b/library/PolyVoxCore/include/Volume.inl index 91f5f38f..9663e8c4 100644 --- a/library/PolyVoxCore/include/Volume.inl +++ b/library/PolyVoxCore/include/Volume.inl @@ -49,8 +49,16 @@ namespace PolyVox /// the default if you are not sure what to choose here. //////////////////////////////////////////////////////////////////////////////// template - Volume::Volume(uint16_t uBlockSideLength) + Volume::Volume + ( + polyvox_function&, const Region&)> dataRequiredHandler, + polyvox_function&, const Region&)> dataOverflowHandler, + uint16_t uBlockSideLength + ) { + m_funcDataRequiredHandler = dataRequiredHandler; + m_funcDataOverflowHandler = dataOverflowHandler; + m_bStreamingEnabled = true; //Create a volume of the right size. resize(Region::MaxRegion,uBlockSideLength); } @@ -68,8 +76,19 @@ namespace PolyVox /// the default if you are not sure what to choose here. //////////////////////////////////////////////////////////////////////////////// template - Volume::Volume(int32_t uWidth, int32_t uHeight, int32_t uDepth, uint16_t uBlockSideLength) + Volume::Volume + ( + int32_t uWidth, int32_t uHeight, int32_t uDepth, + polyvox_function&, const Region&)> dataRequiredHandler, + polyvox_function&, const Region&)> dataOverflowHandler, + bool bStreamingEnabled, + uint16_t uBlockSideLength + ) { + m_funcDataRequiredHandler = dataRequiredHandler; + m_funcDataOverflowHandler = dataOverflowHandler; + m_bStreamingEnabled = bStreamingEnabled; + Region regValid(Vector3DInt32(0,0,0), Vector3DInt32(uWidth - 1,uHeight - 1,uDepth - 1)); resize(Region(Vector3DInt32(0,0,0), Vector3DInt32(uWidth - 1,uHeight - 1,uDepth - 1)), uBlockSideLength); } @@ -87,8 +106,19 @@ namespace PolyVox /// the default if you are not sure what to choose here. //////////////////////////////////////////////////////////////////////////////// template - Volume::Volume(const Region& regValid, uint16_t uBlockSideLength) + Volume::Volume + ( + const Region& regValid, + polyvox_function&, const Region&)> dataRequiredHandler, + polyvox_function&, const Region&)> dataOverflowHandler, + bool bStreamingEnabled, + uint16_t uBlockSideLength + ) { + m_funcDataRequiredHandler = dataRequiredHandler; + m_funcDataOverflowHandler = dataOverflowHandler; + m_bStreamingEnabled = bStreamingEnabled; + //Create a volume of the right size. resize(regValid,uBlockSideLength); } @@ -99,7 +129,7 @@ namespace PolyVox template Volume::~Volume() { - typename std::map::iterator i; + std::map::iterator i; for(i = m_pBlocks.begin(); i != m_pBlocks.end(); i = m_pBlocks.begin()) { eraseBlock(i); } @@ -238,11 +268,11 @@ namespace PolyVox /// \param uBlockCacheSize The number of blocks for which uncompressed data can be cached. //////////////////////////////////////////////////////////////////////////////// template - void Volume::setBlockCacheSize(uint16_t uBlockCacheSize) + void Volume::setMaxNumberOfUncompressedBlocks(uint16_t uMaxNumberOfUncompressedBlocks) { clearBlockCache(); - m_uMaxUncompressedBlockCacheSize = uBlockCacheSize; + m_uMaxNumberOfUncompressedBlocks = uMaxNumberOfUncompressedBlocks; } //////////////////////////////////////////////////////////////////////////////// @@ -250,15 +280,18 @@ namespace PolyVox /// \param uMaxBlocks The number of blocks //////////////////////////////////////////////////////////////////////////////// template - void Volume::setMaxBlocksLoaded(uint16_t uMaxBlocks) + void Volume::setMaxNumberOfBlocksInMemory(uint16_t uMaxNumberOfBlocksInMemory) { - if(uMaxBlocks < m_pBlocks.size()) { - std::cout << uMaxBlocks << ", " << m_pBlocks.size() << ", " << m_pBlocks.size() - uMaxBlocks << std::endl; + //FIXME? - I'm concerned about the logic in here... particularly the + //way timestamps are handled. Perhaps extract all timestamps, sort them, + //identify the cut-off point, and then discard those above it? + if(m_pBlocks.size() > uMaxNumberOfBlocksInMemory) + { // we need to unload some blocks - for(int j = 0; j < m_pBlocks.size() - uMaxBlocks; j++) + for(int j = 0; j < m_pBlocks.size() - uMaxNumberOfBlocksInMemory; j++) { - typename std::map::iterator i; - typename std::map::iterator itUnloadBlock = m_pBlocks.begin(); + std::map::iterator i; + std::map::iterator itUnloadBlock = m_pBlocks.begin(); for(i = m_pBlocks.begin(); i != m_pBlocks.end(); i++) { if(i->second.timestamp < itUnloadBlock->second.timestamp) @@ -269,7 +302,7 @@ namespace PolyVox eraseBlock(itUnloadBlock); } } - m_uMaxBlocksLoaded = uMaxBlocks; + m_uMaxNumberOfBlocksInMemory = uMaxNumberOfBlocksInMemory; } //////////////////////////////////////////////////////////////////////////////// @@ -328,7 +361,6 @@ namespace PolyVox for(uint32_t ct = 0; ct < m_vecUncompressedBlockCache.size(); ct++) { m_vecUncompressedBlockCache[ct]->block.compress(); - delete[] m_vecUncompressedBlockCache[ct]->uncompressedData; } m_vecUncompressedBlockCache.clear(); } @@ -362,10 +394,10 @@ namespace PolyVox } m_uTimestamper = 0; - m_uMaxUncompressedBlockCacheSize = 256; + m_uMaxNumberOfUncompressedBlocks = 16; m_uBlockSideLength = uBlockSideLength; m_pUncompressedBorderData = 0; - m_uMaxBlocksLoaded = 4096; + m_uMaxNumberOfBlocksInMemory = 1024; m_v3dLastAccessedBlockPos = Vector3DInt32((std::numeric_limits::max)(), (std::numeric_limits::max)(), (std::numeric_limits::max)()); //An invalid index m_pLastAccessedBlock = 0; @@ -374,7 +406,7 @@ namespace PolyVox m_regValidRegionInBlocks.setLowerCorner(m_regValidRegion.getLowerCorner() / static_cast(uBlockSideLength)); m_regValidRegionInBlocks.setUpperCorner(m_regValidRegion.getUpperCorner() / static_cast(uBlockSideLength)); - setBlockCacheSize(m_uMaxUncompressedBlockCacheSize); + setMaxNumberOfUncompressedBlocks(m_uMaxNumberOfUncompressedBlocks); //Clear the previous data m_pBlocks.clear(); @@ -399,7 +431,7 @@ namespace PolyVox template void Volume::eraseBlock(typename std::map::iterator itBlock) const { - if(dataOverflowHandler) + if(m_funcDataOverflowHandler) { Vector3DInt32 v3dPos = itBlock->first; Vector3DInt32 v3dLower(v3dPos.getX() << m_uBlockSideLengthPower, v3dPos.getY() << m_uBlockSideLengthPower, v3dPos.getZ() << m_uBlockSideLengthPower); @@ -408,7 +440,7 @@ namespace PolyVox Region reg(v3dLower, v3dUpper); ConstVolumeProxy ConstVolumeProxy(*this, reg); - dataOverflowHandler(ConstVolumeProxy, reg); + m_funcDataOverflowHandler(ConstVolumeProxy, reg); } m_pBlocks.erase(itBlock); } @@ -448,68 +480,76 @@ namespace PolyVox //This check should also provide a significant speed boost as usually it is true. if((v3dBlockPos == m_v3dLastAccessedBlockPos) && (m_pLastAccessedBlock != 0)) { - return &(m_pLastAccessedBlock->block); + assert(m_pLastAccessedBlock->m_tUncompressedData); + return m_pLastAccessedBlock; } - typename std::map::iterator itBlock = m_pBlocks.find(v3dBlockPos); + std::map::iterator itBlock = m_pBlocks.find(v3dBlockPos); // check whether the block is already loaded if(itBlock == m_pBlocks.end()) { - // it is not loaded - // check wether another block needs to be unloaded before this one can be loaded - if(m_pBlocks.size() == m_uMaxBlocksLoaded) + //The block is not in the map, so we will have to create a new block and add it. + //Before we do so, we might want to dump some existing data to make space. We + //Only do this if paging is enabled. + if(m_bStreamingEnabled) { - // find the least recently used block - typename std::map::iterator i; - typename std::map::iterator itUnloadBlock = m_pBlocks.begin(); - for(i = m_pBlocks.begin(); i != m_pBlocks.end(); i++) + // check wether another block needs to be unloaded before this one can be loaded + if(m_pBlocks.size() == m_uMaxNumberOfBlocksInMemory) { - if(i->second.timestamp < itUnloadBlock->second.timestamp) + // find the least recently used block + std::map::iterator i; + std::map::iterator itUnloadBlock = m_pBlocks.begin(); + for(i = m_pBlocks.begin(); i != m_pBlocks.end(); i++) { - itUnloadBlock = i; + if(i->second.timestamp < itUnloadBlock->second.timestamp) + { + itUnloadBlock = i; + } } + eraseBlock(itUnloadBlock); } - eraseBlock(itUnloadBlock); } - Vector3DInt32 v3dLower(v3dBlockPos.getX() << m_uBlockSideLengthPower, v3dBlockPos.getY() << m_uBlockSideLengthPower, v3dBlockPos.getZ() << m_uBlockSideLengthPower); - Vector3DInt32 v3dUpper = v3dLower + Vector3DInt32(m_uBlockSideLength-1, m_uBlockSideLength-1, m_uBlockSideLength-1); // create the new block LoadedBlock newBlock(m_uBlockSideLength); itBlock = m_pBlocks.insert(std::make_pair(v3dBlockPos, newBlock)).first; - // fill it with data (well currently fill it with nothingness) - // "load" will actually call setVoxel, which will in turn call this function again but the block will be found - // so this if(itBlock == m_pBlocks.end()) never is entered - - if(dataRequiredHandler) + //We have created the new block. If paging is enabled it should be used to + //fill in the required data. Otherwise it is just left in the default state. + if(m_bStreamingEnabled) { - Region reg(v3dLower, v3dUpper); - ConstVolumeProxy ConstVolumeProxy(*this, reg); - dataRequiredHandler(ConstVolumeProxy, reg); + if(m_funcDataRequiredHandler) + { + // "load" will actually call setVoxel, which will in turn call this function again but the block will be found + // so this if(itBlock == m_pBlocks.end()) never is entered + //FIXME - can we pass the block around so that we don't have to find it again when we recursively call this function? + Vector3DInt32 v3dLower(v3dBlockPos.getX() << m_uBlockSideLengthPower, v3dBlockPos.getY() << m_uBlockSideLengthPower, v3dBlockPos.getZ() << m_uBlockSideLengthPower); + Vector3DInt32 v3dUpper = v3dLower + Vector3DInt32(m_uBlockSideLength-1, m_uBlockSideLength-1, m_uBlockSideLength-1); + Region reg(v3dLower, v3dUpper); + ConstVolumeProxy ConstVolumeProxy(*this, reg); + m_funcDataRequiredHandler(ConstVolumeProxy, reg); + } } - } + } + //Get the block and mark that we accessed it + LoadedBlock& loadedBlock = itBlock->second; + loadedBlock.timestamp = ++m_uTimestamper; m_v3dLastAccessedBlockPos = v3dBlockPos; + m_pLastAccessedBlock = &(loadedBlock.block); - //Get the block - LoadedBlock* block = &(itBlock->second); - - m_uTimestamper++; - block->timestamp = m_uTimestamper; - - if(block->block.m_bIsCompressed == false) - { - m_pLastAccessedBlock = block; - return &(block->block); + if(loadedBlock.block.m_bIsCompressed == false) + { + assert(m_pLastAccessedBlock->m_tUncompressedData); + return m_pLastAccessedBlock; } //Currently we find the oldest block by iterating over the whole array. Of course we could store the blocks sorted by //timestamp (set, priority_queue, etc) but then we'll need to move them around as the timestamp changes. Can come back //to this if it proves to be a bottleneck (compraed to the cost of actually doing the compression/decompression). uint32_t uUncompressedBlockIndex = (std::numeric_limits::max)(); - assert(m_vecUncompressedBlockCache.size() <= m_uMaxUncompressedBlockCacheSize); - if(m_vecUncompressedBlockCache.size() == m_uMaxUncompressedBlockCacheSize) + assert(m_vecUncompressedBlockCache.size() <= m_uMaxNumberOfUncompressedBlocks); + if(m_vecUncompressedBlockCache.size() == m_uMaxNumberOfUncompressedBlocks) { int32_t leastRecentlyUsedBlockIndex = -1; uint32_t uLeastRecentTimestamp = (std::numeric_limits::max)(); // you said not int64 ;) @@ -524,18 +564,22 @@ namespace PolyVox uUncompressedBlockIndex = leastRecentlyUsedBlockIndex; m_vecUncompressedBlockCache[leastRecentlyUsedBlockIndex]->block.compress(); - m_vecUncompressedBlockCache[leastRecentlyUsedBlockIndex]->uncompressedData = 0; + m_vecUncompressedBlockCache[leastRecentlyUsedBlockIndex] = &loadedBlock; + //m_vecUncompressedBlockCache[leastRecentlyUsedBlockIndex]->block.m_tUncompressedData = 0; } else { - block->uncompressedData = new VoxelType[m_uBlockSideLength * m_uBlockSideLength * m_uBlockSideLength]; - m_vecUncompressedBlockCache.push_back(block); + //loadedBlock.block.m_tUncompressedData = new VoxelType[m_uBlockSideLength * m_uBlockSideLength * m_uBlockSideLength]; + m_vecUncompressedBlockCache.push_back(&loadedBlock); uUncompressedBlockIndex = m_vecUncompressedBlockCache.size() - 1; } - block->block.uncompress(block->uncompressedData); + + loadedBlock.block.uncompress(); + //m_vecUncompressedBlockCache.push_back(&loadedBlock); - m_pLastAccessedBlock = block; - return &(block->block); + m_pLastAccessedBlock = &(loadedBlock.block); + assert(m_pLastAccessedBlock->m_tUncompressedData); + return m_pLastAccessedBlock; } template @@ -552,7 +596,7 @@ namespace PolyVox uint32_t uSizeInBytes = sizeof(Volume); //Memory used by the blocks - typename std::map::iterator i; + std::map::iterator i; for(i = m_pBlocks.begin(); i != m_pBlocks.end(); i++) { //Inaccurate - account for rest of loaded block. diff --git a/tests/testvolume.cpp b/tests/testvolume.cpp index 9f739950..39ea2b41 100644 --- a/tests/testvolume.cpp +++ b/tests/testvolume.cpp @@ -32,7 +32,7 @@ using namespace PolyVox; void TestVolume::testSize() { const int32_t g_uVolumeSideLength = 128; - Volume volData; + Volume volData(g_uVolumeSideLength, g_uVolumeSideLength, g_uVolumeSideLength); //Note: Deliberatly go past each edge by one to test if the bounds checking works. for (int32_t z = 0; z < g_uVolumeSideLength + 1; z++)