1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 18:35:20 +00:00
OpenMW/apps/openmw/engine.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1037 lines
36 KiB
C++
Raw Normal View History

#include "engine.hpp"
2013-01-12 17:52:26 -04:00
#include <cerrno>
2020-06-25 21:46:07 +02:00
#include <chrono>
#include <future>
#include <system_error>
2015-05-13 15:03:21 +02:00
2015-06-03 17:25:18 +02:00
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers>
2013-11-16 10:31:46 +01:00
#include <SDL.h>
2018-08-14 23:05:43 +04:00
#include <components/debug/debuglog.hpp>
2018-10-26 15:18:38 +01:00
#include <components/debug/gldebug.hpp>
2018-08-14 23:05:43 +04:00
2015-04-22 17:58:55 +02:00
#include <components/misc/rng.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
2015-05-13 15:03:21 +02:00
#include <components/sdlutil/imagetosurface.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/resource/stats.hpp>
2013-08-07 13:16:20 -04:00
#include <components/compiler/extensions0.hpp>
#include <components/stereo/multiview.hpp>
#include <components/stereo/stereomanager.hpp>
2017-02-14 03:37:45 +01:00
#include <components/sceneutil/workqueue.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/version/version.hpp>
2022-10-02 22:38:37 +02:00
#include <components/l10n/manager.hpp>
#include <components/loadinglistener/asynclistener.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/frameratelimiter.hpp>
#include <components/sceneutil/color.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/screencapture.hpp>
#include <components/sceneutil/unrefqueue.hpp>
#include <components/sceneutil/util.hpp>
2022-05-13 18:58:00 -07:00
#include <components/settings/shadermanager.hpp>
2023-05-22 14:23:06 +02:00
#include <components/settings/values.hpp>
2022-05-13 18:58:00 -07:00
2015-05-03 17:24:35 +02:00
#include "mwinput/inputmanagerimp.hpp"
2010-07-03 15:41:20 +02:00
#include "mwgui/windowmanagerimp.hpp"
2011-10-09 13:12:44 +02:00
2020-12-18 23:21:10 +01:00
#include "mwlua/luamanagerimp.hpp"
2022-10-06 01:45:45 +02:00
#include "mwlua/worker.hpp"
2020-12-18 23:21:10 +01:00
2012-11-10 14:31:58 +01:00
#include "mwscript/interpretercontext.hpp"
#include "mwscript/scriptmanagerimp.hpp"
#include "mwsound/soundmanagerimp.hpp"
#include "mwworld/class.hpp"
#include "mwworld/datetimemanager.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"
2010-08-06 18:01:34 +02:00
#include "mwmechanics/mechanicsmanagerimp.hpp"
2013-11-16 10:31:46 +01:00
#include "mwstate/statemanagerimp.hpp"
2012-01-10 00:34:29 -05:00
2022-10-06 01:35:39 +02:00
#include "profile.hpp"
2015-06-12 01:08:58 +02:00
namespace
{
void checkSDLError(int ret)
{
if (ret != 0)
2018-08-14 23:05:43 +04:00
Log(Debug::Error) << "SDL error: " << SDL_GetError();
2015-06-12 01:08:58 +02:00
}
2020-06-28 01:44:56 +02:00
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;
2022-10-06 01:35:39 +02:00
OMW::forEachUserStatsValue([&](const OMW::UserStats& v) {
2020-06-28 01:44:56 +02:00
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");
2020-06-28 01:44:56 +02:00
}
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);
2022-06-06 22:40:38 +02:00
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxTextureImageUnits);
}
2022-06-06 22:40:38 +02:00
int getMaxTextureImageUnits() const
{
if (mMaxTextureImageUnits == 0)
throw std::logic_error("mMaxTextureImageUnits is not initialized");
return mMaxTextureImageUnits;
}
private:
int mMaxTextureImageUnits = 0;
};
2015-06-12 01:08:58 +02:00
}
void OMW::Engine::executeLocalScripts()
{
MWWorld::LocalScripts& localScripts = mWorld->getLocalScripts();
localScripts.startIteration();
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
std::pair<ESM::RefId, MWWorld::Ptr> 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)
{
2022-10-06 01:56:47 +02:00
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();
2022-10-06 01:56:47 +02:00
mEnvironment.setFrameDuration(frametime);
2022-10-06 01:56:47 +02:00
try
{
// update input
2020-06-28 01:44:56 +02:00
{
ScopedProfile<UserStatsType::Input> profile(frameStart, frameNumber, *timer, *stats);
mInputManager->update(frametime, false);
2020-06-28 01:44:56 +02:00
}
// 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)
{
2020-06-28 01:44:56 +02:00
ScopedProfile<UserStatsType::Sound> profile(frameStart, frameNumber, *timer, *stats);
if (!mWindowManager->isWindowVisible())
2020-06-28 01:44:56 +02:00
{
mSoundManager->pausePlayback();
2020-06-28 01:44:56 +02:00
return false;
}
else
mSoundManager->resumePlayback();
2020-06-28 01:44:56 +02:00
// sound
if (mUseSound)
mSoundManager->update(frametime);
2020-06-28 01:44:56 +02:00
}
2010-10-31 12:23:03 -04:00
{
ScopedProfile<UserStatsType::LuaSyncUpdate> 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
2020-06-28 01:44:56 +02:00
{
ScopedProfile<UserStatsType::State> profile(frameStart, frameNumber, *timer, *stats);
mStateManager->update(frametime);
2020-06-28 01:44:56 +02:00
}
bool paused = mWorld->getTimeManager()->isPaused();
2013-12-16 21:02:21 +02:00
{
2020-06-28 01:44:56 +02:00
ScopedProfile<UserStatsType::Script> profile(frameStart, frameNumber, *timer, *stats);
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{
if (!mWindowManager->containsMode(MWGui::GM_MainMenu))
2015-02-10 20:25:57 +01:00
{
if (mWorld->getScriptsEnabled())
2020-06-28 01:44:56 +02:00
{
// local scripts
executeLocalScripts();
2020-06-28 01:44:56 +02:00
// global scripts
mScriptManager->getGlobalScripts().run();
2020-06-28 01:44:56 +02:00
}
mWorld->getWorldScene().markCellAsUnchanged();
2020-06-28 01:44:56 +02:00
}
if (!paused)
2020-06-28 01:44:56 +02:00
{
double hours = (frametime * mWorld->getTimeManager()->getGameTimeScale()) / 3600.0;
mWorld->advanceTime(hours, true);
mWorld->rechargeItems(frametime, true);
2020-06-28 01:44:56 +02:00
}
}
2013-12-16 21:02:21 +02:00
}
2020-06-28 01:44:56 +02:00
// update mechanics
{
2020-06-28 01:44:56 +02:00
ScopedProfile<UserStatsType::Mechanics> profile(frameStart, frameNumber, *timer, *stats);
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
2020-06-28 01:44:56 +02:00
{
mMechanicsManager->update(frametime, paused);
2020-06-28 01:44:56 +02:00
}
if (mStateManager->getState() == MWBase::StateManager::State_Running)
2020-06-28 01:44:56 +02:00
{
MWWorld::Ptr player = mWorld->getPlayerPtr();
if (!paused && player.getClass().getCreatureStats(player).isDead())
mStateManager->endGame();
2020-06-28 01:44:56 +02:00
}
}
2010-07-22 12:29:23 +02:00
// update physics
{
2020-06-28 01:44:56 +02:00
ScopedProfile<UserStatsType::Physics> profile(frameStart, frameNumber, *timer, *stats);
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
2020-06-28 01:44:56 +02:00
{
mWorld->updatePhysics(frametime, paused, frameStart, frameNumber, *stats);
2020-06-28 01:44:56 +02:00
}
}
// update world
{
2020-06-28 01:44:56 +02:00
ScopedProfile<UserStatsType::World> profile(frameStart, frameNumber, *timer, *stats);
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
2020-06-28 01:44:56 +02:00
{
mWorld->update(frametime, paused);
2020-06-28 01:44:56 +02:00
}
}
2012-04-14 17:47:44 +02:00
// update GUI
2020-06-28 01:44:56 +02:00
{
ScopedProfile<UserStatsType::Gui> profile(frameStart, frameNumber, *timer, *stats);
mWindowManager->update(frametime);
2020-06-28 01:44:56 +02:00
}
2022-10-06 01:56:47 +02:00
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Error in frame: " << e.what();
}
2022-10-06 01:56:47 +02:00
const bool reportResource = stats->collectStats("resource");
2022-10-06 01:56:47 +02:00
if (reportResource)
stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getSize());
2022-10-06 01:56:47 +02:00
mUnrefQueue->flush(*mWorkQueue);
2022-10-06 01:56:47 +02:00
if (reportResource)
{
stats->setAttribute(frameNumber, "FrameNumber", frameNumber);
2022-10-06 01:56:47 +02:00
mResourceSystem->reportStats(frameNumber, stats);
2022-10-06 01:56:47 +02:00
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
2019-03-17 20:18:53 +03:00
2022-10-06 01:56:47 +02:00
mMechanicsManager->reportStats(frameNumber, *stats);
mWorld->reportStats(frameNumber, *stats);
mLuaManager->reportStats(frameNumber, *stats);
}
2022-10-06 01:56:47 +02:00
mViewer->eventTraversal();
mViewer->updateTraversal();
{
2022-10-06 01:56:47 +02:00
ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats);
mWorld->updateWindowManager();
}
2022-10-06 01:56:47 +02:00
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)
2018-10-09 10:21:12 +04:00
: mWindow(nullptr)
, mEncoding(ToUTF8::WINDOWS_1252)
2018-08-01 20:18:37 +04:00
, mScreenCaptureOperation(nullptr)
, mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation())
, mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation())
, mStereoManager(nullptr)
2013-11-16 11:33:20 +01:00
, mSkipMenu(false)
2010-08-18 11:16:15 +02:00
, mUseSound(true)
2010-10-06 14:52:53 +02:00
, mCompileAll(false)
, mCompileAllDialogue(false)
2014-02-02 14:09:59 +01:00
, mWarningsMode(1)
, mScriptConsoleMode(false)
2013-07-31 18:46:32 +02:00
, mActivationDistanceOverride(-1)
, mGrab(true)
2019-03-19 09:12:31 +04:00
, mRandomSeed(0)
2015-04-25 13:37:42 -05:00
, mScriptBlacklistUse(true)
2014-09-01 11:55:12 +02:00
, mNewGame(false)
2015-05-23 22:44:00 +02:00
, mCfgMgr(configurationManager)
2022-07-02 23:25:51 +04:00
, 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)
{
2014-01-11 11:00:34 +01:00
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;
2022-10-06 01:56:47 +02:00
mLuaWorker = nullptr;
mLuaManager = nullptr;
2022-10-02 22:38:37 +02:00
mL10nManager = nullptr;
2018-10-09 10:21:12 +04:00
mScriptContext = nullptr;
2015-05-13 03:36:20 +02:00
mUnrefQueue = nullptr;
2018-10-09 10:21:12 +04:00
mWorkQueue = nullptr;
2017-02-14 03:37:45 +01:00
2018-10-09 10:21:12 +04:00
mViewer = nullptr;
mResourceSystem.reset();
mEncoder = nullptr;
2015-05-15 19:34:18 +02:00
if (mWindow)
{
SDL_DestroyWindow(mWindow);
2018-10-09 10:21:12 +04:00
mWindow = nullptr;
2015-05-15 19:34:18 +02:00
}
SDL_Quit();
}
// Set data dir
void OMW::Engine::setDataDirs(const Files::PathContainer& dataDirs)
{
mDataDirs = dataDirs;
mDataDirs.insert(mDataDirs.begin(), mResDir / "vfs");
2023-05-31 23:11:03 +02:00
mFileCollections = Files::Collections(mDataDirs);
}
// 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)
{
2014-05-21 15:39:58 +04:00
mResDir = parResDir;
}
// Set start cell name
void OMW::Engine::setCell(const std::string& cellName)
{
2023-01-19 17:31:45 +01:00
mCellName = cellName;
}
void OMW::Engine::addContentFile(const std::string& file)
{
mContentFiles.push_back(file);
}
2020-01-12 11:42:47 +04:00
void OMW::Engine::addGroundcoverFile(const std::string& file)
{
mGroundcoverFiles.emplace_back(file);
}
2014-09-01 11:55:12 +02:00
void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame)
{
2013-11-16 11:33:20 +01:00
mSkipMenu = skipMenu;
2014-09-01 11:55:12 +02:00
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::WindowMode>(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);
2022-05-08 22:56:35 -07:00
if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen)
{
pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen);
pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen);
}
2013-11-16 10:31:46 +01:00
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
if (windowMode == Settings::WindowMode::Fullscreen)
flags |= SDL_WINDOW_FULLSCREEN;
2022-05-08 22:56:35 -07:00
else if (windowMode == Settings::WindowMode::WindowedFullscreen)
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
2021-11-07 20:01:07 -08:00
// 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");
2015-06-12 01:08:58 +02:00
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));
2020-09-19 23:30:34 +01:00
if (Debug::shouldDebugOpenGL())
checkSDLError(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG));
if (antialiasing > 0)
{
2015-06-12 01:08:58 +02:00
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1));
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
}
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> 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<osg::GraphicsContext::Traits> 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.";
2021-01-09 14:44:15 +00:00
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<osg::Camera> camera = mViewer->getCamera();
2015-05-13 15:08:47 +02:00
camera->setGraphicsContext(graphicsWindow);
camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
osg::ref_ptr<SceneUtil::OperationSequence> realizeOperations = new SceneUtil::OperationSequence(false);
mViewer->setRealizeOperation(realizeOperations);
2022-06-06 22:40:38 +02:00
osg::ref_ptr<IdentifyOpenGLOperation> identifyOp = new IdentifyOpenGLOperation();
realizeOperations->add(identifyOp);
2020-09-19 23:30:34 +01:00
if (Debug::shouldDebugOpenGL())
realizeOperations->add(new Debug::EnableGLDebugOperation());
2018-10-25 00:07:01 +01:00
realizeOperations->add(mSelectDepthFormatOperation);
realizeOperations->add(mSelectColorFormatOperation);
if (Stereo::getStereo())
realizeOperations->add(new Stereo::InitializeStereoOperation());
mViewer->realize();
2022-06-06 22:40:38 +02:00
mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits();
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(
0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
}
2015-05-13 15:03:21 +02:00
void OMW::Engine::setWindowIcon()
{
2022-05-25 18:29:02 +00:00
std::ifstream windowIconStream;
const auto windowIcon = mResDir / "openmw.png";
2015-07-17 21:41:53 -05:00
windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary);
2015-05-13 15:03:21 +02:00
if (windowIconStream.fail())
Log(Debug::Error) << "Error: Failed to open " << windowIcon;
2015-05-13 15:03:21 +02:00
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png");
2015-06-05 02:57:31 +02:00
if (!reader)
{
2018-08-14 23:05:43 +04:00
Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found";
2015-06-05 02:57:31 +02:00
return;
}
2015-05-13 15:03:21 +02:00
osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream);
if (!result.success())
Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code "
<< result.status();
2015-05-13 15:03:21 +02:00
else
{
osg::ref_ptr<osg::Image> image = result.getImage();
auto surface = SDLUtil::imageToSurface(image, true);
SDL_SetWindowIcon(mWindow, surface.get());
2015-05-13 15:03:21 +02:00
}
}
void OMW::Engine::prepareEngine()
{
mStateManager = std::make_unique<MWState::StateManager>(mCfgMgr.getUserDataPath() / "saves", mContentFiles);
mEnvironment.setStateManager(*mStateManager);
bool stereoEnabled
= Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo();
mStereoManager = std::make_unique<Stereo::Manager>(mViewer, stereoEnabled);
osg::ref_ptr<osg::Group> rootNode(new osg::Group);
2015-04-24 23:30:30 +02:00
mViewer->setSceneData(rootNode);
createWindow();
2023-05-31 23:11:03 +02:00
mVFS = std::make_unique<VFS::Manager>();
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
2022-01-29 23:21:39 +02:00
mResourceSystem = std::make_unique<Resource::ResourceSystem>(mVFS.get());
2022-06-06 22:40:38 +02:00
mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits);
mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(
false); // keep to Off for now to allow better state sharing
mResourceSystem->getSceneManager()->setFilterSettings(Settings::general().mTextureMagFilter,
Settings::general().mTextureMinFilter, Settings::general().mTextureMipmap, Settings::general().mAnisotropy);
mEnvironment.setResourceSystem(*mResourceSystem);
2023-05-22 14:23:06 +02:00
mWorkQueue = new SceneUtil::WorkQueue(Settings::cells().mPreloadNumThreads);
mUnrefQueue = std::make_unique<SceneUtil::UnrefQueue>();
2017-02-14 03:37:45 +01:00
mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation(mWorkQueue,
new SceneUtil::WriteScreenshotToFileOperation(mCfgMgr.getScreenshotPath(),
Settings::general().mScreenshotFormat,
Settings::general().mNotifyOnSavedScreenshot
? std::function<void(std::string)>(ScheduleNonDialogMessageBox{})
: std::function<void(std::string)>(IgnoreString{})));
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
mViewer->addEventHandler(mScreenCaptureHandler);
2022-10-02 22:38:37 +02:00
mL10nManager = std::make_unique<l10n::Manager>(mVFS.get());
mL10nManager->setPreferredLocales(Settings::general().mPreferredLocales, Settings::general().mGmstOverridesL10n);
2022-10-02 22:38:37 +02:00
mEnvironment.setL10nManager(*mL10nManager);
mLuaManager = std::make_unique<MWLua::LuaManager>(mVFS.get(), mResDir / "lua_libs");
mEnvironment.setLuaManager(*mLuaManager);
2020-12-18 23:21:10 +01:00
// 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";
2022-05-25 18:29:02 +00:00
bool keybinderUserExists = std::filesystem::exists(keybinderUser);
2015-01-24 16:44:17 -06:00
if (!keybinderUserExists)
{
const auto input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml");
2022-05-25 18:29:02 +00:00
if (std::filesystem::exists(input2))
{
keybinderUserExists = std::filesystem::copy_file(input2, keybinderUser);
Log(Debug::Info) << "Loading keybindings file: " << keybinderUser;
}
2015-01-24 16:44:17 -06:00
}
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";
2019-08-03 19:55:58 +00:00
std::filesystem::path userGameControllerdb;
2022-05-25 18:29:02 +00:00
if (std::filesystem::exists(userdefault))
2019-08-03 19:55:58 +00:00
userGameControllerdb = userdefault;
std::filesystem::path gameControllerdb;
2022-05-25 18:29:02 +00:00
if (std::filesystem::exists(localdefault))
gameControllerdb = localdefault;
2022-05-25 18:29:02 +00:00
else if (std::filesystem::exists(globaldefault))
gameControllerdb = globaldefault;
2022-05-04 22:33:39 +02:00
// else if it doesn't exist, pass in an empty string
2021-07-22 15:55:30 -07:00
// gui needs our shaders path before everything else
mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders");
2021-07-22 15:55:30 -07:00
osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f);
2021-11-06 04:38:43 -07:00
#if OSG_VERSION_LESS_THAN(3, 6, 6)
// hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028
if (exts)
exts->glRenderbufferStorageMultisampleCoverageNV = nullptr;
#endif
2015-04-24 21:55:30 +02:00
osg::ref_ptr<osg::Group> guiRoot = new osg::Group;
guiRoot->setName("GUI Root");
guiRoot->setNodeMask(MWRender::Mask_GUI);
mStereoManager->disableStereoForNode(guiRoot);
2015-04-24 21:55:30 +02:00
rootNode->addChild(guiRoot);
2022-01-29 23:21:39 +02:00
mWindowManager = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(),
mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding,
Version::getOpenmwVersionDescription(mResDir), shadersSupported, mCfgMgr);
mEnvironment.setWindowManager(*mWindowManager);
mInputManager = std::make_unique<MWInput::InputManager>(mWindow, mViewer, mScreenCaptureHandler,
mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
mEnvironment.setInputManager(*mInputManager);
// Create sound system
mSoundManager = std::make_unique<MWSound::SoundManager>(mVFS.get(), mUseSound);
mEnvironment.setSoundManager(*mSoundManager);
// Create the world
mWorld = std::make_unique<MWWorld::World>(
mResourceSystem.get(), mActivationDistanceOverride, mCellName, mCfgMgr.getUserDataPath());
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::AsyncListener asyncListener(*listener);
auto dataLoading = std::async(std::launch::async,
[&] { mWorld->loadData(mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder.get(), &asyncListener); });
if (!mSkipMenu)
{
2022-08-28 17:20:49 +02:00
std::string_view logo = Fallback::Map::getString("Movies_Company_Logo");
if (!logo.empty())
mWindowManager->playVideo(logo, true);
}
listener->loadingOn();
{
using namespace std::chrono_literals;
while (dataLoading.wait_for(50ms) != std::future_status::ready)
asyncListener.update();
dataLoading.get();
}
listener->loadingOff();
mWorld->init(mViewer, rootNode, mWorkQueue.get(), *mUnrefQueue);
mWorld->setupPlayer();
mWorld->setRandomSeed(mRandomSeed);
mEnvironment.setWorld(*mWorld);
mEnvironment.setWorldModel(mWorld->getWorldModel());
mEnvironment.setWorldScene(mWorld->getWorldScene());
2023-04-20 21:07:53 +02:00
mEnvironment.setESMStore(mWorld->getStore());
2023-03-12 00:34:17 +01:00
const MWWorld::Store<ESM::GameSetting>* gmst = &mWorld->getStore().get<ESM::GameSetting>();
mL10nManager->setGmstLoader(
[gmst, misses = std::set<std::string, std::less<>>()](std::string_view gmstName) mutable {
const ESM::GameSetting* res = gmst->search(gmstName);
if (res && res->mValue.getType() == ESM::VT_String)
return res->mValue.getString();
else
{
if (misses.count(gmstName) == 0)
{
misses.emplace(gmstName);
Log(Debug::Error) << "GMST " << gmstName << " not found";
}
return std::string("GMST:") + std::string(gmstName);
}
});
2023-03-12 00:34:17 +01:00
mWindowManager->setStore(mWorld->getStore());
mWindowManager->initUI();
2012-12-23 23:23:24 +04:00
// Load translation data
mTranslationDataStorage.setEncoder(mEncoder.get());
for (auto& mContentFile : mContentFiles)
mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFile);
2012-12-23 23:23:24 +04:00
2013-11-16 10:31:46 +01:00
Compiler::registerExtensions(mExtensions);
// Create script system
mScriptContext = std::make_unique<MWScript::CompilerContext>(MWScript::CompilerContext::Type_Full);
2010-07-03 12:12:13 +02:00
mScriptContext->setExtensions(&mExtensions);
2010-07-02 17:21:27 +02:00
mScriptManager = std::make_unique<MWScript::ScriptManager>(mWorld->getStore(), *mScriptContext, mWarningsMode,
mScriptBlacklistUse ? mScriptBlacklist : std::vector<ESM::RefId>());
mEnvironment.setScriptManager(*mScriptManager);
// Create game mechanics system
mMechanicsManager = std::make_unique<MWMechanics::MechanicsManager>();
mEnvironment.setMechanicsManager(*mMechanicsManager);
2010-08-06 18:01:34 +02:00
// Create dialog system
mJournal = std::make_unique<MWDialogue::Journal>();
mEnvironment.setJournal(*mJournal);
mDialogueManager = std::make_unique<MWDialogue::DialogueManager>(mExtensions, mTranslationDataStorage);
mEnvironment.setDialogueManager(*mDialogueManager);
2010-08-06 18:01:34 +02:00
2010-10-06 14:52:53 +02:00
// scripts
if (mCompileAll)
{
std::pair<int, int> result = mScriptManager->compileAll();
if (result.first)
Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts ("
<< 100 * static_cast<double>(result.second) / result.first << "%)";
2010-10-06 14:52:53 +02:00
}
if (mCompileAllDialogue)
{
std::pair<int, int> result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode);
if (result.first)
Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue scripts ("
<< 100 * static_cast<double>(result.second) / result.first << "%)";
}
2020-12-18 23:21:10 +01:00
mLuaManager->init();
mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath());
// starts a separate lua thread if "lua num threads" > 0
mLuaWorker = std::make_unique<MWLua::Worker>(*mLuaManager, *mViewer);
}
// Initialise and enter main loop.
void OMW::Engine::go()
{
assert(!mContentFiles.empty());
2015-04-24 23:30:30 +02:00
2018-08-14 23:05:43 +04:00
Log(Debug::Info) << "OSG version: " << osgGetVersion();
2019-03-15 19:04:47 +03:00
SDL_version sdlVersion;
SDL_GetVersion(&sdlVersion);
Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "."
<< (int)sdlVersion.patch;
2017-02-18 03:15:15 +01:00
Misc::Rng::init(mRandomSeed);
Settings::ShaderManager::get().load(mCfgMgr.getUserConfigPath() / "shaders.yaml");
2022-05-13 18:58:00 -07:00
MWClass::registerClasses();
// Create encoder
mEncoder = std::make_unique<ToUTF8::Utf8Encoder>(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
2022-07-07 19:34:18 +04:00
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler(stats.is_open(), mVFS.get());
2020-06-28 01:44:56 +02:00
initStatsHandler(*statshandler);
mViewer->addEventHandler(statshandler);
2022-07-07 19:34:18 +04:00
osg::ref_ptr<Resource::StatsHandler> 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);
2023-01-21 01:16:08 +03:00
mSoundManager->playPlaylist("Title");
2022-08-28 17:20:49 +02:00
std::string_view logo = Fallback::Map::getString("Movies_Morrowind_Logo");
2019-03-04 01:31:51 +03:00
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
MWWorld::DateTimeManager& timeManager = *mWorld->getTimeManager();
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::chrono::duration<double>>(
std::min(frameRateLimiter.getLastFrameDuration(), maxSimulationInterval))
2022-07-03 12:51:28 +00:00
.count()
* timeManager.getSimulationTimeScale();
mViewer->advance(timeManager.getSimulationTime());
if (!frame(dt))
{
2020-06-25 21:46:07 +02:00
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
timeManager.updateIsPaused();
if (!timeManager.isPaused())
timeManager.setSimulationTime(timeManager.getSimulationTime() + dt);
if (stats)
{
constexpr unsigned statsReportDelay = 3;
const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber();
if (frameNumber >= statsReportDelay)
2020-07-11 16:16:44 +02:00
{
const unsigned reportFrameNumber = frameNumber - statsReportDelay;
mViewer->getViewerStats()->report(stats, reportFrameNumber);
2020-07-11 16:16:44 +02:00
osgViewer::Viewer::Cameras cameras;
mViewer->getCameras(cameras);
for (auto camera : cameras)
camera->getStats()->report(stats, reportFrameNumber);
2020-07-11 16:16:44 +02:00
}
}
frameRateLimiter.limit();
}
2022-10-06 01:56:47 +02:00
mLuaWorker->join();
2020-12-18 23:21:10 +01:00
// Save user settings
Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg");
2022-05-13 18:58:00 -07:00
Settings::ShaderManager::get().save();
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath());
2018-08-14 23:05:43 +04:00
Log(Debug::Info) << "Quitting peacefully.";
}
2010-10-06 14:52:53 +02:00
void OMW::Engine::setCompileAll(bool all)
{
mCompileAll = all;
}
void OMW::Engine::setCompileAllDialogue(bool all)
{
mCompileAllDialogue = all;
}
void OMW::Engine::setSoundUsage(bool soundUsage)
{
mUseSound = soundUsage;
}
2012-12-23 23:23:24 +04:00
void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding)
{
mEncoding = encoding;
}
void OMW::Engine::setScriptConsoleMode(bool enabled)
{
mScriptConsoleMode = enabled;
}
2012-07-30 12:37:46 +02:00
void OMW::Engine::setStartupScript(const std::filesystem::path& path)
2012-07-30 12:37:46 +02:00
{
mStartupScript = path;
}
void OMW::Engine::setActivationDistanceOverride(int distance)
{
mActivationDistanceOverride = distance;
}
2014-02-02 14:09:59 +01:00
void OMW::Engine::setWarningsMode(int mode)
{
mWarningsMode = mode;
}
2014-07-21 09:34:10 +02:00
void OMW::Engine::setScriptBlacklist(const std::vector<ESM::RefId>& list)
2014-07-21 09:34:10 +02:00
{
mScriptBlacklist = list;
}
void OMW::Engine::setScriptBlacklistUse(bool use)
{
mScriptBlacklistUse = use;
2014-08-11 20:37:29 +02:00
}
void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame)
{
mSaveGameFile = savegame;
}
void OMW::Engine::setRandomSeed(unsigned int seed)
{
mRandomSeed = seed;
}