#include "engine.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" #include "mwlua/luamanagerimp.hpp" #include "mwlua/worker.hpp" #include "mwscript/interpretercontext.hpp" #include "mwscript/scriptmanagerimp.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" #include "mwworld/worldimp.hpp" #include "mwrender/vismask.hpp" #include "mwclass/classes.hpp" #include "mwdialogue/dialoguemanagerimp.hpp" #include "mwdialogue/journalimp.hpp" #include "mwdialogue/scripttest.hpp" #include "mwmechanics/mechanicsmanagerimp.hpp" #include "mwstate/statemanagerimp.hpp" #include "profile.hpp" namespace { void checkSDLError(int ret) { if (ret != 0) Log(Debug::Error) << "SDL error: " << SDL_GetError(); } void initStatsHandler(Resource::Profiler& profiler) { const osg::Vec4f textColor(1.f, 1.f, 1.f, 1.f); const osg::Vec4f barColor(1.f, 1.f, 1.f, 1.f); const float multiplier = 1000; const bool average = true; const bool averageInInverseSpace = false; const float maxValue = 10000; OMW::forEachUserStatsValue([&](const OMW::UserStats& v) { profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue); }); // the forEachUserStatsValue loop is "run" at compile time, hence the settings manager is not available. // Unconditionnally add the async physics stats, and then remove it at runtime if necessary if (Settings::Manager::getInt("async num threads", "Physics") == 0) profiler.removeUserStatsLine(" -Async"); } struct ScheduleNonDialogMessageBox { void operator()(std::string message) const { MWBase::Environment::get().getWindowManager()->scheduleMessageBox( std::move(message), MWGui::ShowInDialogueMode_Never); } }; struct IgnoreString { void operator()(std::string) const {} }; class IdentifyOpenGLOperation : public osg::GraphicsOperation { public: IdentifyOpenGLOperation() : GraphicsOperation("IdentifyOpenGLOperation", false) { } void operator()(osg::GraphicsContext* graphicsContext) override { Log(Debug::Info) << "OpenGL Vendor: " << glGetString(GL_VENDOR); Log(Debug::Info) << "OpenGL Renderer: " << glGetString(GL_RENDERER); Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxTextureImageUnits); } int getMaxTextureImageUnits() const { if (mMaxTextureImageUnits == 0) throw std::logic_error("mMaxTextureImageUnits is not initialized"); return mMaxTextureImageUnits; } private: int mMaxTextureImageUnits = 0; }; class InitializeStereoOperation final : public osg::GraphicsOperation { public: InitializeStereoOperation() : GraphicsOperation("InitializeStereoOperation", false) { } void operator()(osg::GraphicsContext* graphicsContext) override { Stereo::Manager::instance().initializeStereo(graphicsContext); } }; } void OMW::Engine::executeLocalScripts() { MWWorld::LocalScripts& localScripts = mWorld->getLocalScripts(); localScripts.startIteration(); std::pair script; while (localScripts.getNext(script)) { MWScript::InterpreterContext interpreterContext(&script.second.getRefData().getLocals(), script.second); mScriptManager->run(script.first, interpreterContext); } } bool OMW::Engine::frame(float frametime) { const osg::Timer_t frameStart = mViewer->getStartTick(); const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); const osg::Timer* const timer = osg::Timer::instance(); osg::Stats* const stats = mViewer->getViewerStats(); mEnvironment.setFrameDuration(frametime); try { // update input { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mInputManager->update(frametime, false); } // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon // changing widget textures (fixed in MyGUI 3.3.2), and destroyed widgets will not be deleted (not fixed yet, // https://github.com/MyGUI/mygui/issues/21) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (!mWindowManager->isWindowVisible()) { mSoundManager->pausePlayback(); return false; } else mSoundManager->resumePlayback(); // sound if (mUseSound) mSoundManager->update(frametime); } // Main menu opened? Then scripts are also paused. bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); // Should be called after input manager update and before any change to the game world. // It applies to the game world queued changes from the previous frame. mLuaManager->synchronizedUpdate(); } // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mStateManager->update(frametime); } bool guiActive = mWindowManager->isGuiMode(); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { if (!paused) { if (mWorld->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts mScriptManager->getGlobalScripts().run(); } mWorld->getWorldScene().markCellAsUnchanged(); } if (!guiActive) { double hours = (frametime * mWorld->getTimeScaleFactor()) / 3600.0; mWorld->advanceTime(hours, true); mWorld->rechargeItems(frametime, true); } } } // update mechanics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { mMechanicsManager->update(frametime, guiActive); } if (mStateManager->getState() == MWBase::StateManager::State_Running) { MWWorld::Ptr player = mWorld->getPlayerPtr(); if (!guiActive && player.getClass().getCreatureStats(player).isDead()) mStateManager->endGame(); } } // update physics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { mWorld->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } // update world { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { mWorld->update(frametime, guiActive); } } // update GUI { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mWindowManager->update(frametime); } } catch (const std::exception& e) { Log(Debug::Error) << "Error in frame: " << e.what(); } const bool reportResource = stats->collectStats("resource"); if (reportResource) stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getSize()); mUnrefQueue->flush(*mWorkQueue); if (reportResource) { stats->setAttribute(frameNumber, "FrameNumber", frameNumber); mResourceSystem->reportStats(frameNumber, stats); stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); mMechanicsManager->reportStats(frameNumber, *stats); mWorld->reportStats(frameNumber, *stats); mLuaManager->reportStats(frameNumber, *stats); } mViewer->eventTraversal(); mViewer->updateTraversal(); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mWorld->updateWindowManager(); } mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now mViewer->renderingTraversals(); mLuaWorker->finishUpdate(); return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mWindow(nullptr) , mEncoding(ToUTF8::WINDOWS_1252) , mScreenCaptureOperation(nullptr) , mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation()) , mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation()) , mStereoManager(nullptr) , mSkipMenu(false) , mUseSound(true) , mCompileAll(false) , mCompileAllDialogue(false) , mWarningsMode(1) , mScriptConsoleMode(false) , mActivationDistanceOverride(-1) , mGrab(true) , mRandomSeed(0) , mFSStrict(false) , mScriptBlacklistUse(true) , mNewGame(false) , mCfgMgr(configurationManager) , mGlMaxTextureImageUnits(0) { SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_SENSOR; if (SDL_WasInit(flags) == 0) { SDL_SetMainReady(); if (SDL_Init(flags) != 0) { throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError())); } } } OMW::Engine::~Engine() { if (mScreenCaptureOperation != nullptr) mScreenCaptureOperation->stop(); mMechanicsManager = nullptr; mDialogueManager = nullptr; mJournal = nullptr; mScriptManager = nullptr; mWindowManager = nullptr; mWorld = nullptr; mStereoManager = nullptr; mSoundManager = nullptr; mInputManager = nullptr; mStateManager = nullptr; mLuaWorker = nullptr; mLuaManager = nullptr; mL10nManager = nullptr; mScriptContext = nullptr; mUnrefQueue = nullptr; mWorkQueue = nullptr; mViewer = nullptr; mResourceSystem.reset(); mEncoder = nullptr; if (mWindow) { SDL_DestroyWindow(mWindow); mWindow = nullptr; } SDL_Quit(); } void OMW::Engine::enableFSStrict(bool fsStrict) { mFSStrict = fsStrict; } // Set data dir void OMW::Engine::setDataDirs(const Files::PathContainer& dataDirs) { mDataDirs = dataDirs; mDataDirs.insert(mDataDirs.begin(), mResDir / "vfs"); mFileCollections = Files::Collections(mDataDirs, !mFSStrict); } // Add BSA archive void OMW::Engine::addArchive(const std::string& archive) { mArchives.push_back(archive); } // Set resource dir void OMW::Engine::setResourceDir(const std::filesystem::path& parResDir) { mResDir = parResDir; } // Set start cell name void OMW::Engine::setCell(const std::string& cellName) { mCellName = cellName; } void OMW::Engine::addContentFile(const std::string& file) { mContentFiles.push_back(file); } void OMW::Engine::addGroundcoverFile(const std::string& file) { mGroundcoverFiles.emplace_back(file); } void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame) { mSkipMenu = skipMenu; mNewGame = newGame; } void OMW::Engine::createWindow() { int screen = Settings::Manager::getInt("screen", "Video"); int width = Settings::Manager::getInt("resolution x", "Video"); int height = Settings::Manager::getInt("resolution y", "Video"); Settings::WindowMode windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); bool windowBorder = Settings::Manager::getBool("window border", "Video"); int vsync = Settings::Manager::getInt("vsync mode", "Video"); unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video")); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen) { pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); } Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; if (windowMode == Settings::WindowMode::Fullscreen) flags |= SDL_WINDOW_FULLSCREEN; else if (windowMode == Settings::WindowMode::WindowedFullscreen) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; // Allows for Windows snapping features to properly work in borderless window SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1"); SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24)); if (Debug::shouldDebugOpenGL()) checkSDLError(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG)); if (antialiasing > 0) { checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); } osg::ref_ptr graphicsWindow; while (!graphicsWindow || !graphicsWindow->valid()) { while (!mWindow) { mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags); if (!mWindow) { // Try with a lower AA if (antialiasing > 0) { Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing / 2; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } else { std::stringstream error; error << "Failed to create SDL window: " << SDL_GetError(); throw std::runtime_error(error.str()); } } } // Since we use physical resolution internally, we have to create the window with scaled resolution, // but we can't get the scale before the window exists, so instead we have to resize aftewards. int w, h; SDL_GetWindowSize(mWindow, &w, &h); int dw, dh; SDL_GL_GetDrawableSize(mWindow, &dw, &dh); if (dw != w || dh != h) { SDL_SetWindowSize(mWindow, width / (dw / w), height / (dh / h)); } setWindowIcon(); osg::ref_ptr traits = new osg::GraphicsContext::Traits; SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); SDL_GL_GetDrawableSize(mWindow, &traits->width, &traits->height); traits->windowName = SDL_GetWindowTitle(mWindow); traits->windowDecoration = !(SDL_GetWindowFlags(mWindow) & SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); traits->vsync = 0; traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits, vsync); if (!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext"); if (traits->samples < antialiasing) { Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " << antialiasing << "x. Trying " << antialiasing / 2 << "x instead."; graphicsWindow->closeImplementation(); SDL_DestroyWindow(mWindow); mWindow = nullptr; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } if (traits->red < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->red << " bit red channel."; if (traits->green < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel."; if (traits->blue < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel."; if (traits->depth < 24) Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision."; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel } osg::ref_ptr camera = mViewer->getCamera(); camera->setGraphicsContext(graphicsWindow); camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); osg::ref_ptr realizeOperations = new SceneUtil::OperationSequence(false); mViewer->setRealizeOperation(realizeOperations); osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); realizeOperations->add(identifyOp); if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); realizeOperations->add(mSelectDepthFormatOperation); realizeOperations->add(mSelectColorFormatOperation); if (Stereo::getStereo()) { realizeOperations->add(new InitializeStereoOperation()); Stereo::setVertexBufferHint(); } mViewer->realize(); mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle( 0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); } void OMW::Engine::setWindowIcon() { std::ifstream windowIconStream; const auto windowIcon = mResDir / "openmw.png"; windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); if (windowIconStream.fail()) Log(Debug::Error) << "Error: Failed to open " << windowIcon; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!reader) { Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); if (!result.success()) Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status(); else { osg::ref_ptr image = result.getImage(); auto surface = SDLUtil::imageToSurface(image, true); SDL_SetWindowIcon(mWindow, surface.get()); } } void OMW::Engine::prepareEngine() { mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mEnvironment.setStateManager(*mStateManager); mStereoManager = std::make_unique(mViewer); osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); createWindow(); mVFS = std::make_unique(mFSStrict); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem = std::make_unique(mVFS.get()); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( false); // keep to Off for now to allow better state sharing mResourceSystem->getSceneManager()->setFilterSettings(Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), Settings::Manager::getInt("anisotropy", "General")); mEnvironment.setResourceSystem(*mResourceSystem); int numThreads = Settings::Manager::getInt("preload num threads", "Cells"); if (numThreads <= 0) throw std::runtime_error("Invalid setting: 'preload num threads' must be >0"); mWorkQueue = new SceneUtil::WorkQueue(numThreads); mUnrefQueue = std::make_unique(); mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation(mWorkQueue, new SceneUtil::WriteScreenshotToFileOperation(mCfgMgr.getScreenshotPath(), Settings::Manager::getString("screenshot format", "General"), Settings::Manager::getBool("notify on saved screenshot", "General") ? std::function(ScheduleNonDialogMessageBox{}) : std::function(IgnoreString{}))); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); mViewer->addEventHandler(mScreenCaptureHandler); mL10nManager = std::make_unique(mVFS.get()); mL10nManager->setPreferredLocales(Settings::Manager::getStringArray("preferred locales", "General")); mEnvironment.setL10nManager(*mL10nManager); mLuaManager = std::make_unique(mVFS.get(), mResDir / "lua_libs"); mEnvironment.setLuaManager(*mLuaManager); // starts a separate lua thread if "lua num threads" > 0 mLuaWorker = std::make_unique(*mLuaManager, *mViewer); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so const auto keybinderUser = mCfgMgr.getUserConfigPath() / "input_v3.xml"; bool keybinderUserExists = std::filesystem::exists(keybinderUser); if (!keybinderUserExists) { const auto input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml"); if (std::filesystem::exists(input2)) { keybinderUserExists = std::filesystem::copy_file(input2, keybinderUser); Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; } } else Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; const auto userdefault = mCfgMgr.getUserConfigPath() / "gamecontrollerdb.txt"; const auto localdefault = mCfgMgr.getLocalPath() / "gamecontrollerdb.txt"; const auto globaldefault = mCfgMgr.getGlobalPath() / "gamecontrollerdb.txt"; std::filesystem::path userGameControllerdb; if (std::filesystem::exists(userdefault)) userGameControllerdb = userdefault; std::filesystem::path gameControllerdb; if (std::filesystem::exists(localdefault)) gameControllerdb = localdefault; else if (std::filesystem::exists(globaldefault)) gameControllerdb = globaldefault; // else if it doesn't exist, pass in an empty string // gui needs our shaders path before everything else mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders"); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 if (exts) exts->glRenderbufferStorageMultisampleCoverageNV = nullptr; #endif osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); mStereoManager->disableStereoForNode(guiRoot); rootNode->addChild(guiRoot); mWindowManager = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, Version::getOpenmwVersionDescription(mResDir), shadersSupported, mCfgMgr); mEnvironment.setWindowManager(*mWindowManager); mInputManager = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); mEnvironment.setInputManager(*mInputManager); // Create sound system mSoundManager = std::make_unique(mVFS.get(), mUseSound); mEnvironment.setSoundManager(*mSoundManager); if (!mSkipMenu) { std::string_view logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) mWindowManager->playVideo(logo, true); } // Create the world mWorld = std::make_unique(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), *mUnrefQueue, mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder.get(), mActivationDistanceOverride, mCellName, mCfgMgr.getUserDataPath()); mWorld->setupPlayer(); mWorld->setRandomSeed(mRandomSeed); mEnvironment.setWorld(*mWorld); mEnvironment.setWorldModel(mWorld->getWorldModel()); mEnvironment.setWorldScene(mWorld->getWorldScene()); const MWWorld::Store* gmst = &mWorld->getStore().get(); mL10nManager->setGmstLoader([gmst](std::string_view gmstName) { const ESM::GameSetting* res = gmst->search(gmstName); if (res && res->mValue.getType() == ESM::VT_String) return res->mValue.getString(); else { Log(Debug::Error) << "GMST " << gmstName << " not found"; return std::string("GMST:") + std::string(gmstName); } }); mWindowManager->setStore(mWorld->getStore()); mWindowManager->initUI(); // Load translation data mTranslationDataStorage.setEncoder(mEncoder.get()); for (auto& mContentFile : mContentFiles) mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFile); Compiler::registerExtensions(mExtensions); // Create script system mScriptContext = std::make_unique(MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions(&mExtensions); mScriptManager = std::make_unique(mWorld->getStore(), *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector()); mEnvironment.setScriptManager(*mScriptManager); // Create game mechanics system mMechanicsManager = std::make_unique(); mEnvironment.setMechanicsManager(*mMechanicsManager); // Create dialog system mJournal = std::make_unique(); mEnvironment.setJournal(*mJournal); mDialogueManager = std::make_unique(mExtensions, mTranslationDataStorage); mEnvironment.setDialogueManager(*mDialogueManager); // scripts if (mCompileAll) { std::pair result = mScriptManager->compileAll(); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts (" << 100 * static_cast(result.second) / result.first << "%)"; } if (mCompileAllDialogue) { std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue scripts (" << 100 * static_cast(result.second) / result.first << "%)"; } mLuaManager->init(); mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath()); } // Initialise and enter main loop. void OMW::Engine::go() { assert(!mContentFiles.empty()); Log(Debug::Info) << "OSG version: " << osgGetVersion(); SDL_version sdlVersion; SDL_GetVersion(&sdlVersion); Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch; Misc::Rng::init(mRandomSeed); Settings::ShaderManager::get().load(mCfgMgr.getUserConfigPath() / "shaders.yaml"); MWClass::registerClasses(); // Create encoder mEncoder = std::make_unique(mEncoding); // Setup viewer mViewer = new osgViewer::Viewer; mViewer->setReleaseContextAtEndOfFrameHint(false); // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); prepareEngine(); #ifdef _WIN32 const auto* stats_file = _wgetenv(L"OPENMW_OSG_STATS_FILE"); #else const auto* stats_file = std::getenv("OPENMW_OSG_STATS_FILE"); #endif std::filesystem::path path; if (stats_file != nullptr) path = stats_file; std::ofstream stats; if (!path.empty()) { stats.open(path, std::ios_base::out); if (stats.is_open()) Log(Debug::Info) << "OSG stats will be written to: " << path; else Log(Debug::Warning) << "Failed to open file to write OSG stats \"" << path << "\": " << std::generic_category().message(errno); } // Setup profiler osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open(), mVFS.get()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open(), mVFS.get()); mViewer->addEventHandler(resourceshandler); if (stats.is_open()) Resource::CollectStatistics(mViewer); // Start the game if (!mSaveGameFile.empty()) { mStateManager->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu mWindowManager->pushGuiMode(MWGui::GM_MainMenu); mSoundManager->playPlaylist("Title"); std::string_view logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) mWindowManager->playVideo(logo, /*allowSkipping*/ true, /*overrideSounds*/ false); } else { mStateManager->newGame(!mNewGame); } if (!mStartupScript.empty() && mStateManager->getState() == MWState::StateManager::State_Running) { mWindowManager->executeInConsole(mStartupScript); } // Start the main rendering loop double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); while (!mViewer->done() && !mStateManager->hasQuitRequest()) { const double dt = std::chrono::duration_cast>( std::min(frameRateLimiter.getLastFrameDuration(), maxSimulationInterval)) .count() * mEnvironment.getWorld()->getSimulationTimeScale(); mViewer->advance(simulationTime); if (!frame(dt)) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } else { bool guiActive = mWindowManager->isGuiMode(); if (!guiActive) simulationTime += dt; } if (stats) { constexpr unsigned statsReportDelay = 3; const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (frameNumber >= statsReportDelay) { const unsigned reportFrameNumber = frameNumber - statsReportDelay; mViewer->getViewerStats()->report(stats, reportFrameNumber); osgViewer::Viewer::Cameras cameras; mViewer->getCameras(cameras); for (auto camera : cameras) camera->getStats()->report(stats, reportFrameNumber); } } frameRateLimiter.limit(); } mLuaWorker->join(); // Save user settings Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg"); Settings::ShaderManager::get().save(); mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath()); Log(Debug::Info) << "Quitting peacefully."; } void OMW::Engine::setCompileAll(bool all) { mCompileAll = all; } void OMW::Engine::setCompileAllDialogue(bool all) { mCompileAllDialogue = all; } void OMW::Engine::setSoundUsage(bool soundUsage) { mUseSound = soundUsage; } void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; } void OMW::Engine::setScriptConsoleMode(bool enabled) { mScriptConsoleMode = enabled; } void OMW::Engine::setStartupScript(const std::string& path) { mStartupScript = path; } void OMW::Engine::setActivationDistanceOverride(int distance) { mActivationDistanceOverride = distance; } void OMW::Engine::setWarningsMode(int mode) { mWarningsMode = mode; } void OMW::Engine::setScriptBlacklist(const std::vector& list) { mScriptBlacklist = list; } void OMW::Engine::setScriptBlacklistUse(bool use) { mScriptBlacklistUse = use; } void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame) { mSaveGameFile = savegame; } void OMW::Engine::setRandomSeed(unsigned int seed) { mRandomSeed = seed; }