diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index 9840fb96d9..c13962fc08 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -103,8 +103,6 @@ public: void writeDocumentation(); void toggleShutdownMode(); - void postLoadingScreenMessage(std::string message); - void runPostInitializationScripts(const std::string& sceneDescription); // Guaranteed to return a valid pointer @@ -112,6 +110,7 @@ public: LuaConsole& console(); DownloadManager& downloadManager(); ModuleEngine& moduleEngine(); + LoadingScreen& loadingScreen(); NetworkEngine& networkEngine(); ParallelConnection& parallelConnection(); RenderEngine& renderEngine(); diff --git a/include/openspace/rendering/loadingscreen.h b/include/openspace/rendering/loadingscreen.h index 3d5f8ccfeb..1bd02e6900 100644 --- a/include/openspace/rendering/loadingscreen.h +++ b/include/openspace/rendering/loadingscreen.h @@ -30,6 +30,7 @@ #include #include +#include namespace ghoul::fontrendering { class Font; @@ -49,7 +50,18 @@ public: void render(); - void queueMessage(std::string message); + void postMessage(std::string message); + + + enum class ItemStatus { + Started, + Initializing, + Finished + }; + + void updateItem(const std::string& itemName, ItemStatus newStatus); + + private: std::unique_ptr _program; @@ -57,14 +69,30 @@ private: std::shared_ptr _loadingFont; std::shared_ptr _messageFont; + std::shared_ptr _itemFont; struct { GLuint vao; GLuint vbo; } _logo; - std::vector _messageQueue; - std::mutex _messageQueueMutex; + std::string _message; + std::mutex _messageMutex; + + + struct Item { + std::string name; + ItemStatus status; + bool hasLocation; + glm::vec2 ll; + glm::vec2 ur; + std::chrono::system_clock::time_point finishedTime; + }; + std::vector _items; + std::mutex _itemsMutex; + + std::random_device _randomDevice; + std::default_random_engine _randomEngine; }; } // namespace openspace diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 8085a45a49..50ee0671f9 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -633,7 +633,7 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) { _renderEngine->startFading(1, 3.0); scene->initialize(); - postLoadingScreenMessage("Finished initializing"); + _loadingScreen->postMessage("Finished initializing"); initializeFinished = true; }); @@ -1388,10 +1388,6 @@ void OpenSpaceEngine::toggleShutdownMode() { } } -void OpenSpaceEngine::postLoadingScreenMessage(std::string message) { - _loadingScreen->queueMessage(std::move(message)); -} - scripting::LuaLibrary OpenSpaceEngine::luaLibrary() { return { "", @@ -1559,6 +1555,11 @@ TimeManager& OpenSpaceEngine::timeManager() { return *_timeManager; } +LoadingScreen& OpenSpaceEngine::loadingScreen() { + ghoul_assert(_loadingScreen, "Loading Screen must not be nullptr"); + return *_loadingScreen; +} + WindowWrapper& OpenSpaceEngine::windowWrapper() { ghoul_assert(_windowWrapper, "Window Wrapper must not be nullptr"); return *_windowWrapper; diff --git a/src/rendering/loadingscreen.cpp b/src/rendering/loadingscreen.cpp index 5b88d942b6..fdb561f4e8 100644 --- a/src/rendering/loadingscreen.cpp +++ b/src/rendering/loadingscreen.cpp @@ -44,7 +44,7 @@ namespace { const glm::vec2 LogoSize = { 0.4f, 0.4 }; const float LoadingTextPosition = 0.275f; - const float StatusMessageOffset = 0.05f; + const float StatusMessageOffset = 0.225f; const int MaximumMessageQueue = 6; @@ -57,7 +57,9 @@ namespace { namespace openspace { -LoadingScreen::LoadingScreen() { +LoadingScreen::LoadingScreen() + : _randomEngine(_randomDevice()) +{ const glm::vec2 dpiScaling = OsEng.windowWrapper().dpiScaling(); const glm::ivec2 res = glm::vec2(OsEng.windowWrapper().currentWindowResolution()) / dpiScaling; @@ -77,6 +79,13 @@ LoadingScreen::LoadingScreen() { _messageFont = OsEng.fontManager().font( + "Loading", + 22, + ghoul::fontrendering::FontManager::Outline::No, + ghoul::fontrendering::FontManager::LoadGlyphs::No + ); + + _itemFont = OsEng.fontManager().font( "Loading", 13, ghoul::fontrendering::FontManager::Outline::No, @@ -161,16 +170,16 @@ void LoadingScreen::render() { LogoSize.y * textureAspectRatio * screenAspectRatio }; - glm::vec2 ll = { LogoCenter.x - size.x, LogoCenter.y - size.y }; - glm::vec2 ur = { LogoCenter.x + size.x, LogoCenter.y + size.y }; + glm::vec2 logoLl = { LogoCenter.x - size.x, LogoCenter.y - size.y }; + glm::vec2 logoUr = { LogoCenter.x + size.x, LogoCenter.y + size.y }; GLfloat data[] = { - ll.x, ll.y, 0.f, 0.f, - ur.x, ur.y, 1.f, 1.f, - ll.x, ur.y, 0.f, 1.f, - ll.x, ll.y, 0.f, 0.f, - ur.x, ll.y, 1.f, 0.f, - ur.x, ur.y, 1.f, 1.f + logoLl.x, logoLl.y, 0.f, 0.f, + logoUr.x, logoUr.y, 1.f, 1.f, + logoLl.x, logoUr.y, 0.f, 1.f, + logoLl.x, logoLl.y, 0.f, 0.f, + logoUr.x, logoLl.y, 1.f, 0.f, + logoUr.x, logoUr.y, 1.f, 1.f }; glBindVertexArray(_logo.vao); @@ -216,45 +225,151 @@ void LoadingScreen::render() { "Loading." ); + glm::vec2 loadingLl = glm::vec2( + res.x / 2.f - bbox.boundingBox.x / 2.f, + res.y * LoadingTextPosition + ); + glm::vec2 loadingUr = loadingLl + bbox.boundingBox; + renderer.render( *_loadingFont, - glm::vec2(res.x / 2.f - bbox.boundingBox.x / 2.f, res.y * LoadingTextPosition), + loadingLl, glm::vec4(1.f, 1.f, 1.f, 1.f), "%s", "Loading..." ); + glm::vec2 messageLl; + glm::vec2 messageUr; + { + std::lock_guard guard(_messageMutex); + + FR::BoundingBoxInformation bboxMessage = renderer.boundingBox( + *_messageFont, + "%s", + _message.c_str() + ); + + messageLl = glm::vec2( + res.x / 2.f - bboxMessage.boundingBox.x / 2.f, + res.y * StatusMessageOffset + ); + messageUr = messageLl + bboxMessage.boundingBox; + + + renderer.render( + *_messageFont, + messageLl, + glm::vec4(1.f, 1.f, 1.f, 1.f), + "%s", + _message.c_str() + ); + } { - std::lock_guard guard(_messageQueueMutex); + std::lock_guard guard(_itemsMutex); - for (int i = 0; i < _messageQueue.size(); ++i) { - const std::string& message = _messageQueue[i]; + for (Item& item : _items) { + if (!item.hasLocation) { + // Compute a new location + + FR::BoundingBoxInformation b = renderer.boundingBox( + *_itemFont, + "%s", + item.name.c_str() + ); - FR::BoundingBoxInformation bboxMessage = renderer.boundingBox( - *_messageFont, - "%s", - message.c_str() - ); + // The maximum count is in here since we can't control the amount of + // screen estate and the number of nodes. Rather than looping forever + // we make use with an overlap in the worst (=10) case + int MaxCounts = 30; + bool foundSpace = false; + + glm::vec2 ll; + glm::vec2 ur; + for (int i = 0; i < MaxCounts && !foundSpace; ++i) { + std::uniform_int_distribution distX( + 15, + res.x - b.boundingBox.x - 15 + ); + std::uniform_int_distribution distY( + 15, + res.y - b.boundingBox.y - 15 + ); + + ll = { distX(_randomEngine), distY(_randomEngine) }; + ur = ll + b.boundingBox; + + // Test against logo and text + bool logoOverlap = !( + (logoUr.x + 1.f) / 2.f * res.x < ll.x || + (logoLl.x + 1.f) / 2.f * res.x > ur.x || + (logoUr.y + 1.f) / 2.f * res.y < ll.y || + (logoLl.y + 1.f) / 2.f * res.y > ur.y + ); + + bool loadingOverlap = !( + loadingUr.x < ll.x || + loadingLl.x > ur.x || + loadingUr.y < ll.y || + loadingLl.y > ur.y + ); + + bool messageOverlap = !( + messageUr.x < ll.x || + messageLl.x > ur.x || + messageUr.y < ll.y || + messageLl.y > ur.y + ); + + + if (logoOverlap || loadingOverlap || messageOverlap) { + continue; + } + + + // Test against all other boxes + bool overlap = false; + for (const Item& j : _items) { + overlap |= !(j.ur.x < ll.x || j.ll.x > ur.x || j.ur.y < ll.y || j.ll.y > ur.y); + + if (overlap) { + break; + } + } + + if (!overlap) { + break; + } + } + + item.ll = ll; + item.ur = ur; + + item.hasLocation = true; + } + + glm::vec4 color = [status = item.status]() { + switch (status) { + case ItemStatus::Started: + return glm::vec4(0.5f, 0.5f, 0.5f, 1.f); + case ItemStatus::Initializing: + return glm::vec4(0.7f, 0.7f, 0.f, 1.f); + case ItemStatus::Finished: + return glm::vec4(1.f, 1.f, 1.f, 1.f); + } + }(); + renderer.render( - *_messageFont, - glm::vec2( - res.x / 2.f - bboxMessage.boundingBox.x / 2.f, - res.y * StatusMessageOffset + i * bboxMessage.boundingBox.y - ), - glm::vec4( - 1.f, 1.f, 1.f, - glm::mix( - MaximumAlpha, - MinimumAlpha, - static_cast(i) / static_cast(MaximumMessageQueue - 1) - ) - ), + *_itemFont, + item.ll, + color, "%s", - message.c_str() + item.name.c_str() ); } + } glEnable(GL_CULL_FACE); @@ -264,13 +379,42 @@ void LoadingScreen::render() { OsEng.windowWrapper().swapBuffer(); } -void LoadingScreen::queueMessage(std::string message) { - std::lock_guard guard(_messageQueueMutex); - _messageQueue.insert(_messageQueue.begin(), std::move(message)); +void LoadingScreen::postMessage(std::string message) { + std::lock_guard guard(_messageMutex); + _message = std::move(message); +} - // We add one message at a time, so we can also delete one at a time - if (_messageQueue.size() > MaximumMessageQueue) { - _messageQueue.pop_back(); +void LoadingScreen::updateItem(const std::string& itemName, ItemStatus newStatus) { + std::lock_guard guard(_itemsMutex); + + auto it = std::find_if( + _items.begin(), + _items.end(), + [&itemName](const Item& i) { + return i.name == itemName; + } + ); + if (it != _items.end()) { + it->status = newStatus; + if (newStatus == ItemStatus::Finished) { + it->finishedTime = std::chrono::system_clock::now(); + } + } + else { + ghoul_assert( + newStatus == ItemStatus::Started, + "Item '" + itemName + "' did not exist but first message was not Started" + ); + // We are not computing the location in here since doing it this way might stall + // the main thread while trying to find a position for the new item + _items.push_back({ + itemName, + ItemStatus::Started, + false, + {}, + {}, + std::chrono::system_clock::from_time_t(0) + }); } } diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 600559a80f..107a07555a 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include #include #include @@ -214,26 +216,34 @@ void Scene::sortTopologically() { } void Scene::initialize() { - std::vector threads; + unsigned int nThreads = std::thread::hardware_concurrency(); + + ghoul::ThreadPool pool(nThreads == 0 ? 2 : nThreads - 1); + + OsEng.loadingScreen().postMessage("Initializing scene"); + for (SceneGraphNode* node : _topologicallySortedNodes) { - std::thread t( - [node]() { - try { - OsEng.postLoadingScreenMessage(node->name()); - node->initialize(); - } - catch (const ghoul::RuntimeError& e) { - LERROR(node->name() << " not initialized."); - LERRORC(std::string(_loggerCat) + "(" + e.component + ")", e.what()); - } + pool.queue([node](){ + try { + OsEng.loadingScreen().updateItem( + node->name(), + LoadingScreen::ItemStatus::Initializing + ); + node->initialize(); + OsEng.loadingScreen().updateItem( + node->name(), + LoadingScreen::ItemStatus::Finished + ); } - ); - threads.push_back(std::move(t)); + catch (const ghoul::RuntimeError& e) { + LERROR(node->name() << " not initialized."); + LERRORC(std::string(_loggerCat) + "(" + e.component + ")", e.what()); + } + + }); } - for (std::thread& t : threads) { - t.join(); - } + pool.stop(); } void Scene::initializeGL() { diff --git a/src/scene/sceneloader.cpp b/src/scene/sceneloader.cpp index 26568bda9f..963805f888 100644 --- a/src/scene/sceneloader.cpp +++ b/src/scene/sceneloader.cpp @@ -23,6 +23,7 @@ ****************************************************************************************/ #include +#include #include #include #include @@ -135,6 +136,11 @@ std::unique_ptr SceneLoader::loadScene(const std::string& path) { std::unique_ptr rootNode = std::make_unique(); rootNode->setName(SceneGraphNode::RootNodeName); scene->setRoot(std::move(rootNode)); + + OsEng.loadingScreen().updateItem( + SceneGraphNode::RootNodeName, + LoadingScreen::ItemStatus::Started + ); addLoadedNodes(*scene, std::move(allNodes)); @@ -287,6 +293,12 @@ SceneLoader::LoadedNode SceneLoader::loadNode(const ghoul::Dictionary& dictionar std::vector dependencies; std::string nodeName = dictionary.value(KeyName); + OsEng.loadingScreen().updateItem( + nodeName, + LoadingScreen::ItemStatus::Started + ); + + std::string parentName = dictionary.value(KeyParentName); std::unique_ptr node = SceneGraphNode::createFromDictionary(dictionary);