diff --git a/modules/globebrowsing/CMakeLists.txt b/modules/globebrowsing/CMakeLists.txt index e5c31e27ed..3c93f41d47 100644 --- a/modules/globebrowsing/CMakeLists.txt +++ b/modules/globebrowsing/CMakeLists.txt @@ -55,6 +55,7 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/other/gdaldataconverter.h ${CMAKE_CURRENT_SOURCE_DIR}/other/lrucache.h ${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.h + ${CMAKE_CURRENT_SOURCE_DIR}/other/threadpool.h ${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentqueue.h ${CMAKE_CURRENT_SOURCE_DIR}/other/tileprovidermanager.h ${CMAKE_CURRENT_SOURCE_DIR}/other/layeredtextureshaderprovider.h @@ -91,6 +92,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/other/gdaldataconverter.inl ${CMAKE_CURRENT_SOURCE_DIR}/other/lrucache.inl ${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.inl + ${CMAKE_CURRENT_SOURCE_DIR}/other/threadpool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/other/tileprovidermanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/other/layeredtextureshaderprovider.cpp diff --git a/modules/globebrowsing/globes/chunk.cpp b/modules/globebrowsing/globes/chunk.cpp index ee68d162ee..7044d95971 100644 --- a/modules/globebrowsing/globes/chunk.cpp +++ b/modules/globebrowsing/globes/chunk.cpp @@ -102,7 +102,8 @@ namespace openspace { _isVisible &= HorizonCuller::isVisible(myRenderData, _surfacePatch, ellipsoid, maxHeight); } - if (!_isVisible) return WANT_MERGE; + if(!_isVisible && owner()->mergeInvisible) + return WANT_MERGE; Vec3 cameraPosition = myRenderData.camera.position().dvec3(); @@ -129,7 +130,9 @@ namespace openspace { else return DO_NOTHING; } - + void Chunk::render(const RenderData& data) const { + _owner->getPatchRenderer().renderChunk(*this, data); + } } // namespace openspace diff --git a/modules/globebrowsing/globes/chunk.h b/modules/globebrowsing/globes/chunk.h index 5582d90f49..a5f095e1f2 100644 --- a/modules/globebrowsing/globes/chunk.h +++ b/modules/globebrowsing/globes/chunk.h @@ -54,6 +54,7 @@ namespace openspace { /// Updates chunk internally and returns a desired level Status update(const RenderData& data); + void render(const RenderData& data) const; const GeodeticPatch& surfacePatch() const; ChunkedLodGlobe* const owner() const; diff --git a/modules/globebrowsing/globes/chunkedlodglobe.cpp b/modules/globebrowsing/globes/chunkedlodglobe.cpp index ca8e6be1f1..cb63048376 100644 --- a/modules/globebrowsing/globes/chunkedlodglobe.cpp +++ b/modules/globebrowsing/globes/chunkedlodglobe.cpp @@ -95,23 +95,35 @@ namespace openspace { void ChunkedLodGlobe::render(const RenderData& data){ minDistToCamera = INFINITY; - ChunkNode::renderedPatches = 0; + ChunkNode::renderedChunks = 0; - _leftRoot->render(data); - _rightRoot->render(data); + renderChunkTree(_leftRoot.get(), data); + renderChunkTree(_rightRoot.get(), data); //LDEBUG("min distnace to camera: " << minDistToCamera); Vec3 cameraPos = data.camera.position().dvec3(); //LDEBUG("cam pos x: " << cameraPos.x << " y: " << cameraPos.y << " z: " << cameraPos.z); - //LDEBUG("ChunkNode count: " << ChunkNode::instanceCount); - //LDEBUG("RenderedPatches count: " << ChunkNode::renderedPatches); - //LDEBUG(ChunkNode::renderedPatches << " / " << ChunkNode::instanceCount << " chunks rendered"); + //LDEBUG("ChunkNode count: " << ChunkNode::chunkNodeCount); + //LDEBUG("RenderedPatches count: " << ChunkNode::renderedChunks); + //LDEBUG(ChunkNode::renderedChunks << " / " << ChunkNode::chunkNodeCount << " chunks rendered"); + } + + void ChunkedLodGlobe::renderChunkTree(ChunkNode* node, const RenderData& data) const { + node->updateChunkTree(data); + if (renderSmallChunksFirst) { + node->renderReversedBreadthFirst(data); + } + else { + node->renderDepthFirst(data); + } + } void ChunkedLodGlobe::update(const UpdateData& data) { _patchRenderer->update(); + } const Ellipsoid& ChunkedLodGlobe::ellipsoid() const diff --git a/modules/globebrowsing/globes/chunkedlodglobe.h b/modules/globebrowsing/globes/chunkedlodglobe.h index 0e6be5e958..ab1dc68295 100644 --- a/modules/globebrowsing/globes/chunkedlodglobe.h +++ b/modules/globebrowsing/globes/chunkedlodglobe.h @@ -87,13 +87,16 @@ namespace openspace { bool doHorizonCulling = true; bool doFrustumCulling = true; - int numPosZthres; + bool mergeInvisible; float lodScaleFactor; bool initChunkVisible; + bool renderSmallChunksFirst = true; private: + void renderChunkTree(ChunkNode* node, const RenderData& data) const; + // Covers all negative longitudes std::unique_ptr _leftRoot; diff --git a/modules/globebrowsing/globes/chunkindex.h b/modules/globebrowsing/globes/chunkindex.h index 80226aa1e1..0f70e6f690 100644 --- a/modules/globebrowsing/globes/chunkindex.h +++ b/modules/globebrowsing/globes/chunkindex.h @@ -48,7 +48,7 @@ using HashKey = unsigned long; struct ChunkIndex { - + int x, y, level; diff --git a/modules/globebrowsing/globes/chunknode.cpp b/modules/globebrowsing/globes/chunknode.cpp index 9937f2e753..60d70ddebf 100644 --- a/modules/globebrowsing/globes/chunknode.cpp +++ b/modules/globebrowsing/globes/chunknode.cpp @@ -22,13 +22,14 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include #include #include #include +#include #include #include @@ -39,8 +40,8 @@ namespace { namespace openspace { -int ChunkNode::instanceCount = 0; -int ChunkNode::renderedPatches = 0; +int ChunkNode::chunkNodeCount = 0; +int ChunkNode::renderedChunks = 0; ChunkNode::ChunkNode(const Chunk& chunk, ChunkNode* parent) : _chunk(chunk) @@ -50,11 +51,11 @@ ChunkNode::ChunkNode(const Chunk& chunk, ChunkNode* parent) _children[1] = nullptr; _children[2] = nullptr; _children[3] = nullptr; - instanceCount++; + chunkNodeCount++; } ChunkNode::~ChunkNode() { - instanceCount--; + chunkNodeCount--; } bool ChunkNode::isRoot() const { @@ -66,16 +67,8 @@ bool ChunkNode::isLeaf() const { } -void ChunkNode::render(const RenderData& data) { - ghoul_assert(isRoot(), "this method should only be invoked on root"); - //LDEBUG("-------------"); - internalUpdateChunkTree(data); - internalRender(data); -} - - // Returns true or false wether this node can be merge or not -bool ChunkNode::internalUpdateChunkTree(const RenderData& data) { +bool ChunkNode::updateChunkTree(const RenderData& data) { //Geodetic2 center = _chunk.surfacePatch.center(); //LDEBUG("x: " << patch.x << " y: " << patch.y << " level: " << patch.level << " lat: " << center.lat << " lon: " << center.lon); @@ -90,7 +83,7 @@ bool ChunkNode::internalUpdateChunkTree(const RenderData& data) { char requestedMergeMask = 0; for (int i = 0; i < 4; ++i) { - if (_children[i]->internalUpdateChunkTree(data)) { + if (_children[i]->updateChunkTree(data)) { requestedMergeMask |= (1 << i); } } @@ -99,26 +92,54 @@ bool ChunkNode::internalUpdateChunkTree(const RenderData& data) { if (requestedMergeMask == 0xf && _chunk.update(data)) { merge(); - // re-run internalUpdateChunkTree on this, now that this is a leaf node + // re-run updateChunkTree on this, now that this is a leaf node // OBS, this may currently cause a split() again ... - return internalUpdateChunkTree(data); + return updateChunkTree(data); } return false; } } -void ChunkNode::internalRender(const RenderData& data) { +void ChunkNode::renderReversedBreadthFirst(const RenderData& data) { + std::stack S; + std::queue Q; + Q.push(this); + while (Q.size() > 0) { + ChunkNode* node = Q.front(); + Q.pop(); + if (node->isLeaf()) { + if (node->_chunk.isVisible()) { + S.push(node); + } + } + else { + for (int i = 0; i < 4; ++i) { + Q.push(node->_children[i].get()); + } + } + } + + while (S.size() > 0) { + S.top()->renderThisChunk(data); + S.pop(); + } +} + +void ChunkNode::renderThisChunk(const RenderData& data) { + _chunk.render(data); + ChunkNode::renderedChunks++; +} + +void ChunkNode::renderDepthFirst(const RenderData& data) { if (isLeaf()) { if (_chunk.isVisible()) { - ChunkRenderer& patchRenderer = _chunk.owner()->getPatchRenderer(); - patchRenderer.renderChunk(_chunk, data); - ChunkNode::renderedPatches++; + renderThisChunk(data); } } else { for (int i = 0; i < 4; ++i) { - _children[i]->internalRender(data); + _children[i]->renderDepthFirst(data); } } } diff --git a/modules/globebrowsing/globes/chunknode.h b/modules/globebrowsing/globes/chunknode.h index f107466180..9af224db6f 100644 --- a/modules/globebrowsing/globes/chunknode.h +++ b/modules/globebrowsing/globes/chunknode.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -64,19 +65,17 @@ public: const ChunkNode& getChild(Quad quad) const; - void render(const RenderData& data); + void renderDepthFirst(const RenderData& data); - static int instanceCount; - static int renderedPatches; + void renderReversedBreadthFirst(const RenderData& data); + void renderThisChunk(const RenderData& data); + bool updateChunkTree(const RenderData& data); + + static int chunkNodeCount; + static int renderedChunks; private: - - void internalRender(const RenderData& data); - bool internalUpdateChunkTree(const RenderData& data); - - - ChunkNode* _parent; std::unique_ptr _children[4]; diff --git a/modules/globebrowsing/globes/renderableglobe.cpp b/modules/globebrowsing/globes/renderableglobe.cpp index 218f577c59..a69500fd41 100644 --- a/modules/globebrowsing/globes/renderableglobe.cpp +++ b/modules/globebrowsing/globes/renderableglobe.cpp @@ -56,10 +56,10 @@ namespace openspace { , _saveOrThrowCamera(properties::BoolProperty("saveOrThrowCamera", "saveOrThrowCamera")) , doFrustumCulling(properties::BoolProperty("doFrustumCulling", "doFrustumCulling")) , doHorizonCulling(properties::BoolProperty("doHorizonCulling", "doHorizonCulling")) - , numPosZthres(properties::IntProperty("numPosZthres", "numPosZthres", 0, 0, 9)) + , mergeInvisible(properties::BoolProperty("mergeInvisible", "mergeInvisible", true)) , lodScaleFactor(properties::FloatProperty("lodScaleFactor", "lodScaleFactor", 10.0f, 0.0f, 100.0f)) , initChunkVisible(properties::BoolProperty("initChunkVisible", "initChunkVisible", true)) - + , renderSmallChunksFirst(properties::BoolProperty("renderSmallChunksFirst", "renderSmallChunksFirst", true)) { setName("RenderableGlobe"); @@ -67,12 +67,16 @@ namespace openspace { addProperty(_saveOrThrowCamera); addProperty(doFrustumCulling); addProperty(doHorizonCulling); - addProperty(numPosZthres); + addProperty(mergeInvisible); addProperty(lodScaleFactor); addProperty(initChunkVisible); + addProperty(renderSmallChunksFirst); doFrustumCulling.setValue(true); doHorizonCulling.setValue(true); + renderSmallChunksFirst.setValue(true); + + // Read the radii in to its own dictionary Vec3 radii; @@ -96,7 +100,7 @@ namespace openspace { colorTextureDictionary.getValue("FilePath", path); std::shared_ptr colorTextureProvider = std::shared_ptr(new TileProvider( - path, 5000, 512)); + path, 5000, 1024, 60)); _tileProviderManager->addColorTexture(name, colorTextureProvider); } @@ -113,7 +117,7 @@ namespace openspace { heightMapDictionary.getValue("FilePath", path); std::shared_ptr heightMapProvider = std::shared_ptr(new TileProvider( - path, 5000, 128)); + path, 5000, 256, 60)); _tileProviderManager->addHeightMap(name, heightMapProvider); } @@ -158,7 +162,7 @@ namespace openspace { } _chunkedLodGlobe->doFrustumCulling = doFrustumCulling.value(); _chunkedLodGlobe->doHorizonCulling = doHorizonCulling.value(); - _chunkedLodGlobe->numPosZthres = numPosZthres.value(); + _chunkedLodGlobe->mergeInvisible = mergeInvisible.value(); _chunkedLodGlobe->lodScaleFactor= lodScaleFactor.value(); _chunkedLodGlobe->initChunkVisible = initChunkVisible.value(); _distanceSwitch.render(data); diff --git a/modules/globebrowsing/globes/renderableglobe.h b/modules/globebrowsing/globes/renderableglobe.h index 0137e60da2..0567422abe 100644 --- a/modules/globebrowsing/globes/renderableglobe.h +++ b/modules/globebrowsing/globes/renderableglobe.h @@ -65,9 +65,10 @@ public: properties::BoolProperty doFrustumCulling; properties::BoolProperty doHorizonCulling; - properties::IntProperty numPosZthres; + properties::BoolProperty mergeInvisible; properties::FloatProperty lodScaleFactor; properties::BoolProperty initChunkVisible; + properties::BoolProperty renderSmallChunksFirst; private: double _time; diff --git a/modules/globebrowsing/other/concurrentjobmanager.h b/modules/globebrowsing/other/concurrentjobmanager.h index 1a994a3a7f..33cd150b5a 100644 --- a/modules/globebrowsing/other/concurrentjobmanager.h +++ b/modules/globebrowsing/other/concurrentjobmanager.h @@ -83,6 +83,12 @@ namespace openspace { } } + void clearEnqueuedJobs() { + while (_incomingJobs.size()) { + _incomingJobs.pop(); + } + } + std::shared_ptr> popFinishedJob() { ghoul_assert(_finishedJobs.size() > 0, "There is no finished job to pop!"); return _finishedJobs.pop(); diff --git a/modules/globebrowsing/other/threadpool.cpp b/modules/globebrowsing/other/threadpool.cpp new file mode 100644 index 0000000000..8712ed65b6 --- /dev/null +++ b/modules/globebrowsing/other/threadpool.cpp @@ -0,0 +1,120 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + + +namespace openspace { + + Worker::Worker(ThreadPool& pool) + : pool(pool) + { + + } + + void Worker::operator()() { + std::function task; + while (true) { + + // acquire lock + { + std::unique_lock lock(pool.queue_mutex); + + // look for a work item + while (!pool.stop && pool.tasks.empty()) { + // if there are none wait for notification + pool.condition.wait(lock); + } + + if (pool.stop) { // exit if the pool is stopped + return; + } + + // get the task from the queue + task = pool.tasks.front(); + pool.tasks.pop_front(); + + }// release lock + + // execute the task + task(); + } + + + } + + + + + + ThreadPool::ThreadPool(size_t numThreads) + : stop(false) + { + for (size_t i = 0; i < numThreads; ++i) { + workers.push_back(std::thread(Worker(*this))); + } + } + + // the destructor joins all threads + ThreadPool::~ThreadPool() { + // stop all threads + stop = true; + condition.notify_all(); + + // join them + for (size_t i = 0; i < workers.size(); ++i) { + workers[i].join(); + } + } + + + // add new work item to the pool + void ThreadPool::enqueue(std::function f) { + { // acquire lock + std::unique_lock lock(queue_mutex); + + // add the task + tasks.push_back(f); + } // release lock + + // wake up one thread + std::cout << "Notify one thread" << std::endl; + condition.notify_one(); + } + +} // namespace openspace \ No newline at end of file diff --git a/modules/globebrowsing/other/threadpool.h b/modules/globebrowsing/other/threadpool.h new file mode 100644 index 0000000000..73ae6a2d6d --- /dev/null +++ b/modules/globebrowsing/other/threadpool.h @@ -0,0 +1,81 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __THREAD_POOL_H__ +#define __THREAD_POOL_H__ + +#include +#include +#include +#include +#include +#include + +#include + +#include + + + +// Implementatin based on http://progsch.net/wordpress/?p=81 + +namespace openspace { + + + class ThreadPool; + + class Worker { + public: + Worker(ThreadPool& pool); + void operator()(); + private: + ThreadPool& pool; + }; + + class ThreadPool { + public: + ThreadPool(size_t numThreads); + ~ThreadPool(); + + void enqueue(std::function f); + + private: + friend class Worker; + + std::vector workers; + + std::deque> tasks; + + std::mutex queue_mutex; + std::condition_variable condition; + + bool stop; + }; + + +} // namespace openspace + + + +#endif // __THREAD_POOL_H__ diff --git a/modules/globebrowsing/other/tileprovider.cpp b/modules/globebrowsing/other/tileprovider.cpp index b20691a465..601c9ee660 100644 --- a/modules/globebrowsing/other/tileprovider.cpp +++ b/modules/globebrowsing/other/tileprovider.cpp @@ -48,9 +48,12 @@ namespace openspace { TileProvider::TileProvider( const std::string& filePath, int tileCacheSize, - int minimumPixelSize) + int minimumPixelSize, + int framesUntilRequestFlush) : _filePath(filePath) , _tileCache(tileCacheSize) // setting cache size + , _framesSinceLastRequestFlush(0) + , _framesUntilRequestFlush(framesUntilRequestFlush) { // Set a temporary texture std::string fileName = "textures/earth_bluemarble.jpg"; @@ -101,11 +104,20 @@ namespace openspace { } TileProvider::~TileProvider(){ + clearRequestQueue(); delete _gdalDataSet; } void TileProvider::prerender() { + initTexturesFromLoadedData(); + + if (_framesSinceLastRequestFlush++ > _framesUntilRequestFlush) { + clearRequestQueue(); + } + } + + void TileProvider::initTexturesFromLoadedData() { while (_tileLoadManager.numFinishedJobs() > 0) { auto finishedJob = _tileLoadManager.popFinishedJob(); std::shared_ptr uninitedTex = @@ -116,87 +128,93 @@ namespace openspace { } } + void TileProvider::clearRequestQueue() { + _tileLoadManager.clearEnqueuedJobs(); + _queuedTileRequests.clear(); + _framesSinceLastRequestFlush = 0; + } - Tile TileProvider::getMostHiResTile(ChunkIndex chunkIndex) { - std::shared_ptr tex = nullptr; - glm::vec2 uvOffset(0, 0); - glm::vec2 uvScale(1, 1); - - // Check if we are trying to get a texture for a very small patch. - // In that case, use the biggest one defined for the dataset. - int maximumAllowedLevel = - _gdalDataSet->GetRasterBand(1)->GetOverviewCount() - 1; - int levelInDataset = chunkIndex.level + _tileLevelDifference; - int timesToStepUp = levelInDataset - maximumAllowedLevel; - for (int i = 0; i < timesToStepUp; i++) - { - uvScale *= 0.5; - uvOffset *= 0.5; - if (chunkIndex.isEastChild()) { - uvOffset.x += 0.5; - } - - // In OpenGL, positive y direction is up - if (chunkIndex.isNorthChild()) { - uvOffset.y += 0.5; - } + Tile TileProvider::getHighestResolutionTile(ChunkIndex chunkIndex) { + TileUvTransform uvTransform; + uvTransform.uvOffset = glm::vec2(0, 0); + uvTransform.uvScale = glm::vec2(1, 1); + int numOverviews = _gdalDataSet->GetRasterBand(1)->GetOverviewCount(); + int maximumLevel = numOverviews - 1 - _tileLevelDifference; + while(chunkIndex.level > maximumLevel){ + transformFromParent(chunkIndex, uvTransform); chunkIndex = chunkIndex.parent(); } - // We also need to check if the wanted texture is available. If not, go up a level - while (true) { - tex = getOrStartFetchingTile(chunkIndex); - - if (tex != nullptr) { - break; - } - - if (chunkIndex.level <= 1) { - tex = getDefaultTexture(); - break; - } - - // If we have a parent, calculate the UV offset and scale from the chunkIndex - else { - uvScale *= 0.5; - uvOffset *= 0.5; - - if (chunkIndex.isEastChild()) { - uvOffset.x += 0.5; - } - - // In OpenGL, positive y direction is up - if (chunkIndex.isNorthChild()) { - uvOffset.y += 0.5; - } - - chunkIndex = chunkIndex.parent(); - } - } - - return{ tex, {uvOffset, uvScale } }; + return getOrEnqueueHighestResolutionTile(chunkIndex, uvTransform); } + Tile TileProvider::getOrEnqueueHighestResolutionTile(const ChunkIndex& chunkIndex, + TileUvTransform& uvTransform) + { + HashKey key = chunkIndex.hashKey(); + if (_tileCache.exist(key)) { + return { _tileCache.get(key), uvTransform }; + } + else if (chunkIndex.level <= 1) { + return { getDefaultTexture(), uvTransform }; + } + else { + // We don't have the tile for the requested level + // --> check if the parent has a tile we can use + transformFromParent(chunkIndex, uvTransform); + Tile tile = getOrEnqueueHighestResolutionTile(chunkIndex.parent(), uvTransform); + + // As we didn't have this tile, push it to the request queue + // post order enqueueing tiles --> enqueue tiles at low levels first + enqueueTileRequest(chunkIndex); + + return tile; + } + } + + + + void TileProvider::transformFromParent(const ChunkIndex& chunkIndex, TileUvTransform& uv) const { + uv.uvOffset *= 0.5; + uv.uvScale *= 0.5; + + if (chunkIndex.isEastChild()) { + uv.uvOffset.x += 0.5; + } + + // In OpenGL, positive y direction is up + if (chunkIndex.isNorthChild()) { + uv.uvOffset.y += 0.5; + } + } + + std::shared_ptr TileProvider::getOrStartFetchingTile(ChunkIndex chunkIndex) { - HashKey hashkey = chunkIndex.hashKey(); - if (_tileCache.exist(hashkey)) { return _tileCache.get(hashkey); } else { + enqueueTileRequest(chunkIndex); + return nullptr; + } + } + + bool TileProvider::enqueueTileRequest(const ChunkIndex& chunkIndex) { + HashKey key = chunkIndex.hashKey(); + bool tileHasBeenQueued = _queuedTileRequests.find(key) != _queuedTileRequests.end(); + if (!tileHasBeenQueued) { // enque load job std::shared_ptr job = std::shared_ptr( new TextureTileLoadJob(this, chunkIndex)); _tileLoadManager.enqueueJob(job); - // map key to nullptr while tile is loaded - _tileCache.put(hashkey, nullptr); - return nullptr; + _queuedTileRequests.insert(key); } + return !tileHasBeenQueued; } diff --git a/modules/globebrowsing/other/tileprovider.h b/modules/globebrowsing/other/tileprovider.h index 4117b5898d..422c702db6 100644 --- a/modules/globebrowsing/other/tileprovider.h +++ b/modules/globebrowsing/other/tileprovider.h @@ -28,6 +28,7 @@ #include "gdal_priv.h" #include +#include #include #include // absPath @@ -69,10 +70,11 @@ namespace openspace { */ class TileProvider { public: - TileProvider(const std::string& fileName, int tileCacheSize, int minimumPixelSize); + TileProvider(const std::string& fileName, int tileCacheSize, int minimumPixelSize, + int framesUntilRequestFlush); ~TileProvider(); - Tile getMostHiResTile(ChunkIndex chunkIndex); + Tile getHighestResolutionTile(ChunkIndex chunkIndex); std::shared_ptr getOrStartFetchingTile(ChunkIndex chunkIndex); std::shared_ptr getDefaultTexture(); @@ -84,6 +86,16 @@ namespace openspace { friend class TextureTileLoadJob; + + + ////////////////////////////////////////////////////////////////////////////////// + // Helper functions // + ////////////////////////////////////////////////////////////////////////////////// + Tile getOrEnqueueHighestResolutionTile(const ChunkIndex& ci, TileUvTransform& uvTransform); + + + void transformFromParent(const ChunkIndex& ci, TileUvTransform& uv) const; + /** Fetches all the needeed texture data from the GDAL dataset. */ @@ -96,9 +108,27 @@ namespace openspace { std::shared_ptr initializeTexture( std::shared_ptr uninitedTexture); - LRUCache> _tileCache; + bool enqueueTileRequest(const ChunkIndex& ci); - const std::string _filePath; + void clearRequestQueue(); + + void initTexturesFromLoadedData(); + + + + + ////////////////////////////////////////////////////////////////////////////////// + // Member variables // + ////////////////////////////////////////////////////////////////////////////////// + + + LRUCache> _tileCache; + std::set _queuedTileRequests; + + int _framesSinceLastRequestFlush; + int _framesUntilRequestFlush; + + const std::string _filePath; static bool hasInitializedGDAL; GDALDataset* _gdalDataSet; diff --git a/modules/globebrowsing/rendering/culling.cpp b/modules/globebrowsing/rendering/culling.cpp index ba8249c4a5..a8dd94c96d 100644 --- a/modules/globebrowsing/rendering/culling.cpp +++ b/modules/globebrowsing/rendering/culling.cpp @@ -24,7 +24,6 @@ #include -#include #include @@ -48,6 +47,9 @@ namespace openspace { } + const AABB3 FrustumCuller::viewFrustum(vec3(-1, -1, 0), vec3(1, 1, 1e35)); + + bool FrustumCuller::isVisible( const RenderData& data, const vec3& point) { @@ -143,8 +145,8 @@ namespace openspace { bounds.expand(cornerScreenSpace); } - AABB3 viewFrustum(vec3(-1, -1, 0), vec3(1, 1, 1e35)); - return bounds.intersects(viewFrustum); + + return bounds.intersects(FrustumCuller::viewFrustum); /* vec2 center = bounds.center(); diff --git a/modules/globebrowsing/rendering/culling.h b/modules/globebrowsing/rendering/culling.h index 24db4d0ebd..f7d6e92f33 100644 --- a/modules/globebrowsing/rendering/culling.h +++ b/modules/globebrowsing/rendering/culling.h @@ -34,6 +34,8 @@ #include #include +#include + namespace openspace { @@ -52,12 +54,11 @@ namespace openspace { class FrustumCuller { public: - - - FrustumCuller(); ~FrustumCuller(); + static const AABB3 viewFrustum; + /** Returns true if the point is inside the view frustrum defined in RenderData. The third argument marginScreenSpace is added to the default screen space diff --git a/modules/globebrowsing/rendering/patchrenderer.cpp b/modules/globebrowsing/rendering/patchrenderer.cpp index e979894cac..02bc45da48 100644 --- a/modules/globebrowsing/rendering/patchrenderer.cpp +++ b/modules/globebrowsing/rendering/patchrenderer.cpp @@ -176,7 +176,7 @@ namespace openspace { texUnitHeight.push_back(ghoul::opengl::TextureUnit()); auto tileProvider = it->second; // Get the texture that should be used for rendering - Tile tile = tileProvider->getMostHiResTile(chunk.index()); + Tile tile = tileProvider->getHighestResolutionTile(chunk.index()); TileDepthTransform depthTransform = tileProvider->depthTransform(); // The texture needs a unit to sample from @@ -211,7 +211,7 @@ namespace openspace { { auto tileProvider = it->second; // Get the texture that should be used for rendering - Tile tile = tileProvider->getMostHiResTile(chunk.index()); + Tile tile = tileProvider->getHighestResolutionTile(chunk.index()); // The texture needs a unit to sample from texUnitColor[i].activate(); @@ -276,7 +276,7 @@ namespace openspace { //auto tileProviderHeight = heightMapProviders.begin()->second; // Get the textures that should be used for rendering - Tile heightTile = tileProviderHeight->getMostHiResTile(chunk.index()); + Tile heightTile = tileProviderHeight->getHighestResolutionTile(chunk.index()); // Bind and use the texture @@ -296,7 +296,7 @@ namespace openspace { // Pick the first color texture auto colorTextureProviders = _tileProviderManager->colorTextureProviders(); auto tileProviderColor = colorTextureProviders.begin()->second; - Tile colorTile = tileProviderColor->getMostHiResTile(chunk.index()); + Tile colorTile = tileProviderColor->getHighestResolutionTile(chunk.index()); // Bind and use the texture @@ -375,7 +375,7 @@ namespace openspace { auto tileProvider = it->second; // Get the texture that should be used for rendering - Tile tile = tileProvider->getMostHiResTile(chunk.index()); + Tile tile = tileProvider->getHighestResolutionTile(chunk.index()); TileDepthTransform depthTransform = tileProvider->depthTransform(); // The texture needs a unit to sample from @@ -410,7 +410,7 @@ namespace openspace { auto tileProvider = it->second; // Get the texture that should be used for rendering - Tile tile = tileProvider->getMostHiResTile(chunk.index()); + Tile tile = tileProvider->getHighestResolutionTile(chunk.index()); // The texture needs a unit to sample from texUnitColor[i].activate(); @@ -514,7 +514,7 @@ namespace openspace { auto tileProviderHeight = heightMapProviders.begin()->second; // Get the textures that should be used for rendering - Tile heightTile = tileProviderHeight->getMostHiResTile(chunk.index()); + Tile heightTile = tileProviderHeight->getHighestResolutionTile(chunk.index()); // Bind and use the texture ghoul::opengl::TextureUnit texUnitHeight; @@ -531,7 +531,7 @@ namespace openspace { // Pick the first color texture auto colorTextureProviders = _tileProviderManager->colorTextureProviders(); auto tileProviderColor = colorTextureProviders.begin()->second; - Tile colorTile = tileProviderColor->getMostHiResTile(chunk.index()); + Tile colorTile = tileProviderColor->getHighestResolutionTile(chunk.index()); // Bind and use the texture diff --git a/tests/main.cpp b/tests/main.cpp index c52f078c77..33ac9906c1 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -33,15 +33,16 @@ //#include //#include //#include -#include -#include +//#include +//#include +#include //#include //#include -#include +//#include //#include //#include -#include +//#include //#include //#include diff --git a/tests/test_chunknode.inl b/tests/test_chunknode.inl index 72e30d36d0..35f12a0890 100644 --- a/tests/test_chunknode.inl +++ b/tests/test_chunknode.inl @@ -25,7 +25,7 @@ #include "gtest/gtest.h" #include -#include +#include #include #include diff --git a/tests/test_texturetileset.inl b/tests/test_texturetileset.inl deleted file mode 100644 index cc5b4dfa65..0000000000 --- a/tests/test_texturetileset.inl +++ /dev/null @@ -1,143 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2016 * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this * - * software and associated documentation files (the "Software"), to deal in the Software * - * without restriction, including without limitation the rights to use, copy, modify, * - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * - * permit persons to whom the Software is furnished to do so, subject to the following * - * conditions: * - * * - * The above copyright notice and this permission notice shall be included in all copies * - * or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - ****************************************************************************************/ - -#include "gtest/gtest.h" - -#include - -#define _USE_MATH_DEFINES -#include -#include - -class TextureTileSetTest : public testing::Test {}; - -using namespace openspace; - -TEST_F(TextureTileSetTest, getTileIndexLevel) { - - // Create a tile set with maximum depth 0 - TextureTileSet tileSet(Geodetic2(M_PI, M_PI * 2), Geodetic2(M_PI / 2, - M_PI), 0); - - GeodeticPatch patch(Geodetic2(0, 0), Geodetic2(M_PI / 16, M_PI / 8)); - GeodeticTileIndex tileIndex0 = tileSet.getTileIndex(patch); - - // Maximum level is 0 - ASSERT_EQ(tileIndex0.level, 0); - - - // Create a tile set with maximum depth 10 - TextureTileSet tileSetDepth10(Geodetic2(M_PI, M_PI * 2), Geodetic2(M_PI / 2, - M_PI), 10); - - // A big tile that covers the whole latlon space - GeodeticPatch patchBig(Geodetic2(0, 0), Geodetic2(M_PI / 2, M_PI)); - tileIndex0 = tileSetDepth10.getTileIndex(patchBig); - - // Should return 0 since the tile covers the whole latlon space - ASSERT_EQ(tileIndex0.level, 0); - - - // An edge case tile that covers a fourth of the latlon space - GeodeticPatch patchEdgeCase(Geodetic2(0, 0), Geodetic2(M_PI / 4, M_PI / 2)); - GeodeticTileIndex tileIndex1 = tileSetDepth10.getTileIndex(patchEdgeCase); - - // Now it can go up a level - ASSERT_EQ(tileIndex1.level, 1); - - - // Bigger than the edge case - GeodeticPatch patchEdgeCaseBigger(Geodetic2(0, 0), Geodetic2(M_PI / 4 + 0.001, M_PI / 2 + 0.001)); - tileIndex0 = tileSetDepth10.getTileIndex(patchEdgeCaseBigger); - - // Should return 0 again - ASSERT_EQ(tileIndex0.level, 0); -} - -TEST_F(TextureTileSetTest, getTileIndexXY) { - - // Create a tile set with maximum depth 0 - TextureTileSet tileSet(Geodetic2(M_PI, M_PI * 2), Geodetic2(M_PI / 2, - M_PI), 0); - - GeodeticPatch patch(Geodetic2(0, 0), Geodetic2(M_PI / 16, M_PI / 8)); - GeodeticTileIndex tileIndex0 = tileSet.getTileIndex(patch); - - // Maximum level is 0 so the x y indices should also be 0 - ASSERT_EQ(tileIndex0.x, 0); - ASSERT_EQ(tileIndex0.y, 0); - - - // Create a tile set with maximum depth 10 - TextureTileSet tileSetDepth10(Geodetic2(M_PI, M_PI * 2), Geodetic2(M_PI / 2, - M_PI), 10); - - // A big tile that covers the whole latlon space - GeodeticPatch patchBig(Geodetic2(0, 0), Geodetic2(M_PI / 2, M_PI)); - tileIndex0 = tileSetDepth10.getTileIndex(patchBig); - - // Should return 0 in x and y since the tile covers the whole latlon space - ASSERT_EQ(tileIndex0.x, 0); - ASSERT_EQ(tileIndex0.y, 0); - - - // A tile that covers a fourth of the latlon space - GeodeticPatch patchEdgeCase(Geodetic2(0, 0), Geodetic2(M_PI / 4, M_PI / 2)); - GeodeticTileIndex tileIndex1 = tileSetDepth10.getTileIndex(patchEdgeCase); - - // Now it can go up a level (1) - // Since the position is 0, 0 it has 0, 0, in x, y index - ASSERT_EQ(tileIndex1.x, 0); - ASSERT_EQ(tileIndex1.y, 0); - - - // A smaller edge case tile - GeodeticPatch patchEdgeCase2(Geodetic2(0, 0), Geodetic2(M_PI / 8, M_PI / 4)); - GeodeticTileIndex tileIndex11 = tileSetDepth10.getTileIndex(patchEdgeCase2); - - // Now it can go up two levels (2) - // Since the position is 0, 0 it now has 1, 1, in x, y index - // (north west corner is in that tile) - ASSERT_EQ(tileIndex11.x, 1); - ASSERT_EQ(tileIndex11.y, 1); -} - - -TEST_F(TextureTileSetTest, getUvTransformationPatchToTile) { - // Create a tile set with maximum depth 0 - TextureTileSet tileSet(Geodetic2(M_PI, M_PI * 2), Geodetic2(M_PI / 2, -M_PI), 0); - - // Create a patch that covers the whole latlon space - GeodeticPatch patch(Geodetic2(0, 0), Geodetic2(M_PI / 2, M_PI)); - - // Should be a 1:1 mapping - glm::mat3 patchToTileTransform = - tileSet.getUvTransformationPatchToTile(patch, { 0, 0, 0 }); - - ASSERT_EQ(patchToTileTransform, glm::mat3(1)); - - // Create a smaller patch in the upper west side - patch = GeodeticPatch(Geodetic2(M_PI / 4, - M_PI / 2), Geodetic2(M_PI / 4, M_PI / 2)); - patchToTileTransform = - tileSet.getUvTransformationPatchToTile(patch, { 0,0,0 }); - - glm::vec2 uvPatchSpace = glm::vec2(0, 0); - glm::vec2 uvTileSpace = glm::vec2(patchToTileTransform * glm::vec3(uvPatchSpace, 1)); -} diff --git a/tests/test_twmstileprovider.inl b/tests/test_threadpool.inl similarity index 79% rename from tests/test_twmstileprovider.inl rename to tests/test_threadpool.inl index 7896f5fd4f..5d50c15f09 100644 --- a/tests/test_twmstileprovider.inl +++ b/tests/test_threadpool.inl @@ -24,28 +24,36 @@ #include "gtest/gtest.h" -#include -#include +#include + #define _USE_MATH_DEFINES #include #include -class TWMSTileProviderTest : public testing::Test {}; + + + +class ThreadPoolTest : public testing::Test {}; + using namespace openspace; +using namespace std::chrono_literals; -TEST_F(TWMSTileProviderTest, Simple) { - - TileProvider* tileProvider = new TileProvider(); - GeodeticTileIndex tileIndex = { 0, 0, 0 }; - tileProvider->getTile(tileIndex); +TEST_F(ThreadPoolTest, Basic) { + ThreadPool pool(5); - using namespace std::chrono_literals; - std::this_thread::sleep_for(2s); + int val = 0; + + for (int i = 0; i < 10; ++i) { + pool.enqueue([&val, i]() { + std::this_thread::sleep_for(std::chrono::milliseconds(100 + 10*i)); + val++; + }); + } - std::cout << "exiting" << std::endl; - + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + EXPECT_EQ(10, val) << "10 tasks taking 100 to 190 ms on 5 threads should take less than 1000 ms"; }