diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 82019211f..72fdb29ce 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -508,6 +508,15 @@ bool SystemData::loadConfig() return true; } + const bool splashScreen {Settings::getInstance()->getBool("SplashScreen")}; + float systemCount {0.0f}; + float loadedSystems {0.0f}; + + for (pugi::xml_node system {systemList.child("system")}; system; + system = system.next_sibling("system")) { + ++systemCount; + } + for (pugi::xml_node system {systemList.child("system")}; system; system = system.next_sibling("system")) { std::string name; @@ -521,6 +530,13 @@ bool SystemData::loadConfig() sortName = system.child("systemsortname").text().get(); path = system.child("path").text().get(); + if (splashScreen) { + ++loadedSystems; + const float progress {glm::mix(0.0f, 0.5f, loadedSystems / systemCount)}; + Window::getInstance()->renderSplashScreen(Window::SplashScreenState::SCANNING, + progress); + } + auto nameFindFunc = [&] { for (auto system : sSystemVector) { if (system->mName == name) { @@ -581,7 +597,7 @@ bool SystemData::loadConfig() } // Convert extensions list from a string into a vector of strings. - std::vector extensions = readList(system.child("extension").text().get()); + std::vector extensions {readList(system.child("extension").text().get())}; // Load all launch command tags for the system and if there are multiple tags, then // the label attribute needs to be set on all entries as it's a requirement for the diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 87367ee56..7fb8dca4d 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -531,16 +531,15 @@ int main(int argc, char* argv[]) // This is a workaround to disable the incredibly annoying save state functionality in // macOS which forces a restore of the previous window state. The problem is that this // removes the splash screen on startup and it may have other adverse effects as well. - std::string saveStateDir = Utils::FileSystem::expandHomePath( - "~/Library/Saved Application State/org.es-de.EmulationStation.savedState"); + std::string saveStateDir {Utils::FileSystem::expandHomePath( + "~/Library/Saved Application State/org.es-de.EmulationStation.savedState")}; // Deletion of the state files should normally not be required as there shouldn't be any // files to begin with. But maybe the files can still be created for unknown reasons // as macOS really really loves to restore windows. Let's therefore include this deletion // step as an extra precaution. if (Utils::FileSystem::exists(saveStateDir)) { - for (std::string stateFile : Utils::FileSystem::getDirContent(saveStateDir)) { + for (std::string stateFile : Utils::FileSystem::getDirContent(saveStateDir)) Utils::FileSystem::removeFile(stateFile); - } } else { Utils::FileSystem::createDirectory(saveStateDir); @@ -683,8 +682,6 @@ int main(int argc, char* argv[]) window->pushGui(ViewController::getInstance()); - bool splashScreen {Settings::getInstance()->getBool("SplashScreen")}; - InputManager::getInstance().parseEvent(event); if (event.type == SDL_QUIT) return 1; @@ -707,15 +704,18 @@ int main(int argc, char* argv[]) } #endif - if (splashScreen) { - std::string progressText {"Loading system config..."}; - window->renderLoadingScreen(progressText); - } - AudioManager::getInstance(); MameNames::getInstance(); ThemeData::populateThemeSets(); + // We need to temporarily disable VSync as the splash screen may otherwise slow down + // application startup significantly due to excessive swapBuffers() calls. + const bool splashScreen {Settings::getInstance()->getBool("SplashScreen")}; + const bool vSync {Settings::getInstance()->getBool("VSync")}; + if (splashScreen && vSync) + SDL_GL_SetSwapInterval(0); loadSystemsReturnCode loadSystemsStatus {loadSystemConfigFile()}; + if (splashScreen && vSync) + SDL_GL_SetSwapInterval(1); if (loadSystemsStatus) { // If there was an issue parsing the es_systems.xml file, display an error message. @@ -752,9 +752,6 @@ int main(int argc, char* argv[]) << "\""; } - if (splashScreen) - window->renderLoadingScreen("Done"); - // Open the input configuration GUI if the flag to force this was passed from the command line. if (!loadSystemsStatus) { if (forceInputConfig) { diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 1fba34548..82270f0ea 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -241,7 +241,7 @@ void ViewController::goToStart(bool playTransition) void ViewController::ReloadAndGoToStart() { - mWindow->renderLoadingScreen("Loading..."); + mWindow->renderSplashScreen(Window::SplashScreenState::RELOADING, 0.0f); reloadAll(); if (mState.viewing == GAMELIST) { goToSystemView(SystemData::sSystemVector.front(), false); @@ -1108,13 +1108,15 @@ void ViewController::preload() if (!SystemData::sSystemVector.empty()) getSystemListView(); - for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); - ++it) { + float loadedSystems {0.0f}; + + for (auto it = SystemData::sSystemVector.cbegin(); // Line break. + it != SystemData::sSystemVector.cend(); ++it) { if (Settings::getInstance()->getBool("SplashScreen")) { - mWindow->renderLoadingScreen( - "Loading '" + (*it)->getFullName() + "' (" + - std::to_string(std::distance(SystemData::sSystemVector.cbegin(), it) + 1) + "/" + - std::to_string(systemCount) + ")"); + ++loadedSystems; + const float progress { + glm::mix(0.5f, 1.0f, loadedSystems / static_cast(systemCount))}; + mWindow->renderSplashScreen(Window::SplashScreenState::POPULATING, progress); } (*it)->getIndex()->resetFilters(); getGamelistView(*it)->preloadGamelist(); diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index c8a1ed9a1..03ffbbf31 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -3,7 +3,7 @@ // EmulationStation Desktop Edition // Window.cpp // -// Window management, screensaver management, and help prompts. +// Window management, screensaver management, help prompts and splash screen. // The input stack starts here as well, as this is the first instance called by InputManager. // @@ -25,6 +25,8 @@ Window::Window() noexcept : mRenderer {Renderer::getInstance()} + , mSplashTextPositions {0.0f, 0.0f, 0.0f, 0.0f} + , mBackgroundOverlayOpacity {1.0f} , mScreensaver {nullptr} , mMediaViewer {nullptr} , mLaunchScreen {nullptr} @@ -47,23 +49,19 @@ Window::Window() noexcept , mInvalidateCacheTimer {0} , mVideoPlayerCount {0} , mTopScale {0.5f} + , mRenderedHelpPrompts {false} , mChangedThemeSet {false} { } Window::~Window() { - delete mBackgroundOverlay; - delete mSplash; - // Delete all our GUIs. while (peekGui()) delete peekGui(); if (mInfoPopup) delete mInfoPopup; - - delete mHelp; } Window* Window::getInstance() @@ -112,10 +110,10 @@ bool Window::init() ResourceManager::getInstance().reloadAll(); - mHelp = new HelpComponent; - mSplash = new ImageComponent; + mHelp = std::make_unique(); + mSplash = std::make_unique(false, false); - mBackgroundOverlay = new ImageComponent; + mBackgroundOverlay = std::make_unique(false, false); mBackgroundOverlayOpacity = 0.0f; // Keep a reference to the default fonts, so they don't keep getting destroyed/recreated. @@ -135,6 +133,49 @@ bool Window::init() mSplash->setPosition((mRenderer->getScreenWidth() - mSplash->getSize().x) / 2.0f, (mRenderer->getScreenHeight() - mSplash->getSize().y) / 2.0f * 0.6f); + mSplashTextScanning = std::unique_ptr(mDefaultFonts.at(1)->buildTextCache( + "Scanning game files...", 0.0f, 0.0f, DEFAULT_TEXTCOLOR)); + mSplashTextPopulating = std::unique_ptr(mDefaultFonts.at(1)->buildTextCache( + "Populating systems...", 0.0f, 0.0f, DEFAULT_TEXTCOLOR)); + mSplashTextReloading = std::unique_ptr( + mDefaultFonts.at(1)->buildTextCache("Reloading...", 0.0f, 0.0f, DEFAULT_TEXTCOLOR)); + + mSplashTextPositions.x = + (mRenderer->getScreenWidth() - mSplashTextScanning->metrics.size.x) / 2.0f; + mSplashTextPositions.z = + (mRenderer->getScreenWidth() - mSplashTextPopulating->metrics.size.x) / 2.0f; + mSplashTextPositions.w = + (mRenderer->getScreenWidth() - mSplashTextReloading->metrics.size.x) / 2.0f; + mSplashTextPositions.y = mRenderer->getScreenHeight() * 0.745f; + + ProgressBarRectangle progressBarRect; + if (mRenderer->getScreenWidth() > mRenderer->getScreenHeight()) + progressBarRect.barWidth = mRenderer->getScreenHeight() * 0.53f; + else + progressBarRect.barWidth = mRenderer->getScreenWidth() * 0.53f; + progressBarRect.barHeight = mDefaultFonts.at(1)->getLetterHeight() * 1.3f; + progressBarRect.barPosX = + (mRenderer->getScreenWidth() / 2.0f) - (progressBarRect.barWidth / 2.0f); + progressBarRect.barPosY = mSplashTextPositions.y + (progressBarRect.barHeight * 1.65f); + progressBarRect.color = DEFAULT_TEXTCOLOR; + mProgressBarRectangles.emplace_back(progressBarRect); + + const float borderThickness {std::ceil(2.0f * mRenderer->getScreenHeightModifier())}; + + progressBarRect.barWidth -= borderThickness * 2.0f; + progressBarRect.barHeight -= borderThickness * 2.0f; + progressBarRect.barPosX += borderThickness; + progressBarRect.barPosY += borderThickness; + progressBarRect.color = 0x000000FF; + mProgressBarRectangles.emplace_back(progressBarRect); + + progressBarRect.barWidth -= borderThickness * 2.0f; + progressBarRect.barHeight -= borderThickness * 2.0f; + progressBarRect.barPosX += borderThickness; + progressBarRect.barPosY += borderThickness; + progressBarRect.color = 0xA1001DFF; + mProgressBarRectangles.emplace_back(progressBarRect); + mBackgroundOverlay->setImage(":/graphics/frame.png"); mBackgroundOverlay->setResize(mRenderer->getScreenWidth(), mRenderer->getScreenHeight()); @@ -605,23 +646,50 @@ void Window::render() } } -void Window::renderLoadingScreen(std::string text) +void Window::renderSplashScreen(SplashScreenState state, float progress) { glm::mat4 trans {mRenderer->getIdentity()}; mRenderer->setMatrix(trans); mRenderer->drawRect(0.0f, 0.0f, mRenderer->getScreenWidth(), mRenderer->getScreenHeight(), 0x000000FF, 0x000000FF); mSplash->render(trans); - - auto& font = mDefaultFonts.at(1); - TextCache* cache {font->buildTextCache(text, 0.0f, 0.0f, 0x656565FF)}; - - float x {std::round((mRenderer->getScreenWidth() - cache->metrics.size.x) / 2.0f)}; - float y {std::round(mRenderer->getScreenHeight() * 0.835f)}; - trans = glm::translate(trans, glm::round(glm::vec3 {x, y, 0.0f})); mRenderer->setMatrix(trans); - font->renderTextCache(cache); - delete cache; + + if (state != SplashScreenState::RELOADING) { + // We need to render three rectangles: border, black center and actual progress bar. + for (size_t i {0}; i < mProgressBarRectangles.size(); ++i) { + const float rectWidth {i == mProgressBarRectangles.size() - 1 ? progress : 1.0f}; + mRenderer->drawRect( + mProgressBarRectangles.at(i).barPosX, mProgressBarRectangles.at(i).barPosY, + mProgressBarRectangles.at(i).barWidth * rectWidth, + mProgressBarRectangles.at(i).barHeight, mProgressBarRectangles.at(i).color, + mProgressBarRectangles.at(i).color); + } + } + + float textPosX {0.0f}; + float textPosY {mSplashTextPositions.y}; + + if (state == SplashScreenState::SCANNING) { + textPosX = mSplashTextPositions.x; + } + else if (state == SplashScreenState::POPULATING) { + textPosX = mSplashTextPositions.z; + } + else if (state == SplashScreenState::RELOADING) { + textPosX = mSplashTextPositions.w; + textPosY += mDefaultFonts.at(1)->getLetterHeight(); + } + + trans = glm::translate(trans, glm::round(glm::vec3 {textPosX, textPosY, 0.0f})); + mRenderer->setMatrix(trans); + + if (state == SplashScreenState::SCANNING) + mDefaultFonts.at(1)->renderTextCache(mSplashTextScanning.get()); + else if (state == SplashScreenState::POPULATING) + mDefaultFonts.at(1)->renderTextCache(mSplashTextPopulating.get()); + else if (state == SplashScreenState::RELOADING) + mDefaultFonts.at(1)->renderTextCache(mSplashTextReloading.get()); mRenderer->swapBuffers(); } @@ -707,14 +775,6 @@ void Window::setHelpPrompts(const std::vector& prompts, const HelpSt mHelp->setPrompts(addPrompts); } -void Window::reloadHelpPrompts() -{ - if (mHelp) { - delete mHelp; - mHelp = new HelpComponent; - } -} - void Window::stopInfoPopup() { if (mInfoPopup) diff --git a/es-core/src/Window.h b/es-core/src/Window.h index 2b5348979..d47424a2a 100644 --- a/es-core/src/Window.h +++ b/es-core/src/Window.h @@ -3,7 +3,7 @@ // EmulationStation Desktop Edition // Window.h // -// Window management, screensaver management, and help prompts. +// Window management, screensaver management, help prompts and splash screen. // The input stack starts here as well, as this is the first instance called by InputManager. // @@ -92,13 +92,18 @@ public: void normalizeNextUpdate() { mNormalizeNextUpdate = true; } - void renderLoadingScreen(std::string text); + enum class SplashScreenState { + SCANNING, + POPULATING, + RELOADING + }; + + void renderSplashScreen(SplashScreenState state, float progress); // The list scroll overlay is triggered from IList when the highest scrolling tier is reached. void renderListScrollOverlay(const float opacity, const std::string& text); void renderHelpPromptsEarly(); // Used to render HelpPrompts before a fade. void setHelpPrompts(const std::vector& prompts, const HelpStyle& style); - void reloadHelpPrompts(); // GuiInfoPopup notifications. void queueInfoPopup(const std::string& message, const int& duration) @@ -150,10 +155,24 @@ private: // Returns true if at least one component on the stack is processing. bool isProcessing(); + struct ProgressBarRectangle { + float barWidth; + float barHeight; + float barPosX; + float barPosY; + unsigned int color; + }; + Renderer* mRenderer; - HelpComponent* mHelp; - ImageComponent* mBackgroundOverlay; - ImageComponent* mSplash; + std::unique_ptr mHelp; + std::unique_ptr mBackgroundOverlay; + std::unique_ptr mSplash; + std::unique_ptr mSplashTextScanning; + std::unique_ptr mSplashTextPopulating; + std::unique_ptr mSplashTextReloading; + glm::vec4 mSplashTextPositions; + std::vector mProgressBarRectangles; + float mBackgroundOverlayOpacity; std::vector mGuiStack; std::vector> mDefaultFonts; diff --git a/es-core/src/renderers/RendererOpenGL.cpp b/es-core/src/renderers/RendererOpenGL.cpp index edfa9c83a..799d12f24 100644 --- a/es-core/src/renderers/RendererOpenGL.cpp +++ b/es-core/src/renderers/RendererOpenGL.cpp @@ -340,6 +340,7 @@ void RendererOpenGL::setSwapInterval() LOG(LogInfo) << "Enabling VSync..."; } else { + Settings::getInstance()->setBool("VSync", false); LOG(LogWarning) << "Could not enable VSync: " << SDL_GetError(); } }