Engine.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. //
  2. // Copyright (c) 2008-2013 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "Precompiled.h"
  23. #include "Audio.h"
  24. #include "Console.h"
  25. #include "Context.h"
  26. #include "CoreEvents.h"
  27. #include "DebugHud.h"
  28. #include "Engine.h"
  29. #include "FileSystem.h"
  30. #include "Graphics.h"
  31. #include "Input.h"
  32. #include "Log.h"
  33. #include "Network.h"
  34. #include "PackageFile.h"
  35. #include "PhysicsWorld.h"
  36. #include "ProcessUtils.h"
  37. #include "Profiler.h"
  38. #include "Renderer.h"
  39. #include "ResourceCache.h"
  40. #include "Scene.h"
  41. #include "SceneEvents.h"
  42. #include "Script.h"
  43. #include "ScriptAPI.h"
  44. #include "StringUtils.h"
  45. #include "UI.h"
  46. #include "WorkQueue.h"
  47. #include "XMLFile.h"
  48. #include "DebugNew.h"
  49. #if defined(_MSC_VER) && defined(_DEBUG)
  50. // From dbgint.h
  51. #define nNoMansLandSize 4
  52. typedef struct _CrtMemBlockHeader
  53. {
  54. struct _CrtMemBlockHeader* pBlockHeaderNext;
  55. struct _CrtMemBlockHeader* pBlockHeaderPrev;
  56. char* szFileName;
  57. int nLine;
  58. size_t nDataSize;
  59. int nBlockUse;
  60. long lRequest;
  61. unsigned char gap[nNoMansLandSize];
  62. } _CrtMemBlockHeader;
  63. #endif
  64. namespace Urho3D
  65. {
  66. OBJECTTYPESTATIC(Engine);
  67. Engine::Engine(Context* context) :
  68. Object(context),
  69. timeStep_(0.0f),
  70. minFps_(10),
  71. #if defined(ANDROID) || defined(IOS)
  72. maxFps_(60),
  73. maxInactiveFps_(10),
  74. pauseMinimized_(true),
  75. #else
  76. maxFps_(200),
  77. maxInactiveFps_(60),
  78. pauseMinimized_(false),
  79. #endif
  80. initialized_(false),
  81. exiting_(false),
  82. headless_(false),
  83. audioPaused_(false)
  84. {
  85. }
  86. Engine::~Engine()
  87. {
  88. // Remove subsystems that use SDL in reverse order of construction
  89. context_->RemoveSubsystem<Audio>();
  90. context_->RemoveSubsystem<UI>();
  91. context_->RemoveSubsystem<Input>();
  92. context_->RemoveSubsystem<Renderer>();
  93. context_->RemoveSubsystem<Graphics>();
  94. }
  95. bool Engine::Initialize(const String& windowTitle, const String& logName, const Vector<String>& arguments, void* externalWindow)
  96. {
  97. if (initialized_)
  98. return true;
  99. String renderPath;
  100. int width = 0;
  101. int height = 0;
  102. int multiSample = 1;
  103. int buffer = 100;
  104. int mixRate = 44100;
  105. bool fullscreen = true;
  106. bool vsync = false;
  107. bool tripleBuffer = false;
  108. bool forceSM2 = false;
  109. bool shadows = true;
  110. bool lqShadows = false;
  111. bool sound = true;
  112. bool stereo = true;
  113. bool interpolation = true;
  114. bool threads = true;
  115. bool logDebug = false;
  116. for (unsigned i = 0; i < arguments.Size(); ++i)
  117. {
  118. if (arguments[i][0] == '-' && arguments[i].Length() >= 2)
  119. {
  120. String argument = arguments[i].Substring(1).ToLower();
  121. if (argument == "headless")
  122. headless_ = true;
  123. else if (argument == "logdebug")
  124. logDebug = true;
  125. else if (argument == "nolimit")
  126. SetMaxFps(0);
  127. else if (argument == "nosound")
  128. sound = false;
  129. else if (argument == "noip")
  130. interpolation = false;
  131. else if (argument == "mono")
  132. stereo = false;
  133. else if (argument == "prepass")
  134. renderPath = "RenderPaths/Prepass.xml";
  135. else if (argument == "deferred")
  136. renderPath = "RenderPaths/Deferred.xml";
  137. else if (argument == "noshadows")
  138. shadows = false;
  139. else if (argument == "lqshadows")
  140. lqShadows = true;
  141. else if (argument == "nothreads")
  142. threads = false;
  143. else if (argument == "sm2")
  144. forceSM2 = true;
  145. else
  146. {
  147. int value;
  148. if (argument.Length() > 1)
  149. value = ToInt(argument.Substring(1));
  150. switch (tolower(argument[0]))
  151. {
  152. case 'x':
  153. width = value;
  154. break;
  155. case 'y':
  156. height = value;
  157. break;
  158. case 'm':
  159. multiSample = value;
  160. break;
  161. case 'b':
  162. buffer = value;
  163. break;
  164. case 'r':
  165. mixRate = value;
  166. break;
  167. case 'v':
  168. vsync = true;
  169. break;
  170. case 't':
  171. tripleBuffer = true;
  172. break;
  173. case 'f':
  174. fullscreen = true;
  175. break;
  176. case 'w':
  177. fullscreen = false;
  178. break;
  179. }
  180. }
  181. }
  182. }
  183. // Register object factories and attributes first, then subsystems
  184. RegisterObjects();
  185. RegisterSubsystems();
  186. // Start logging
  187. Log* log = GetSubsystem<Log>();
  188. if (log)
  189. {
  190. log->Open(logName);
  191. if (logDebug)
  192. log->SetLevel(LOG_DEBUG);
  193. }
  194. // Set maximally accurate low res timer
  195. GetSubsystem<Time>()->SetTimerPeriod(1);
  196. // Set amount of worker threads according to the available physical CPU cores. Using also hyperthreaded cores results in
  197. // unpredictable extra synchronization overhead. Also reserve one core for the main thread
  198. unsigned numThreads = threads ? GetNumPhysicalCPUs() - 1 : 0;
  199. if (numThreads)
  200. {
  201. GetSubsystem<WorkQueue>()->CreateThreads(numThreads);
  202. LOGINFO(ToString("Created %u worker thread%s", numThreads, numThreads > 1 ? "s" : ""));
  203. }
  204. // Add default resource paths: CoreData package or directory, Data package or directory
  205. ResourceCache* cache = GetSubsystem<ResourceCache>();
  206. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  207. String exePath = fileSystem->GetProgramDir();
  208. if (fileSystem->FileExists(exePath + "CoreData.pak"))
  209. {
  210. SharedPtr<PackageFile> package(new PackageFile(context_));
  211. package->Open(exePath + "CoreData.pak");
  212. cache->AddPackageFile(package);
  213. }
  214. else if (fileSystem->DirExists(exePath + "CoreData"))
  215. cache->AddResourceDir(exePath + "CoreData");
  216. if (fileSystem->FileExists(exePath + "Data.pak"))
  217. {
  218. SharedPtr<PackageFile> package(new PackageFile(context_));
  219. package->Open(exePath + "Data.pak");
  220. cache->AddPackageFile(package);
  221. }
  222. else if (fileSystem->DirExists(exePath + "Data"))
  223. cache->AddResourceDir(exePath + "Data");
  224. // Initialize graphics & audio output
  225. if (!headless_)
  226. {
  227. Graphics* graphics = GetSubsystem<Graphics>();
  228. Renderer* renderer = GetSubsystem<Renderer>();
  229. if (externalWindow)
  230. graphics->SetExternalWindow(externalWindow);
  231. graphics->SetForceSM2(forceSM2);
  232. graphics->SetWindowTitle(windowTitle);
  233. if (!graphics->SetMode(width, height, fullscreen, vsync, tripleBuffer, multiSample))
  234. return false;
  235. if (!renderPath.Empty())
  236. renderer->SetDefaultRenderPath(cache->GetResource<XMLFile>(renderPath));
  237. renderer->SetDrawShadows(shadows);
  238. if (shadows && lqShadows)
  239. renderer->SetShadowQuality(SHADOWQUALITY_LOW_16BIT);
  240. if (sound)
  241. GetSubsystem<Audio>()->SetMode(buffer, mixRate, stereo, interpolation);
  242. }
  243. // Init FPU state of main thread
  244. InitFPU();
  245. frameTimer_.Reset();
  246. initialized_ = true;
  247. return true;
  248. }
  249. bool Engine::InitializeScripting()
  250. {
  251. if (!initialized_)
  252. return false;
  253. // Check if scripting already initialized
  254. if (GetSubsystem<Script>())
  255. return true;
  256. RegisterScriptLibrary(context_);
  257. context_->RegisterSubsystem(new Script(context_));
  258. {
  259. PROFILE(RegisterScriptAPI);
  260. asIScriptEngine* engine = GetSubsystem<Script>()->GetScriptEngine();
  261. RegisterMathAPI(engine);
  262. RegisterCoreAPI(engine);
  263. RegisterIOAPI(engine);
  264. RegisterResourceAPI(engine);
  265. RegisterSceneAPI(engine);
  266. RegisterGraphicsAPI(engine);
  267. RegisterInputAPI(engine);
  268. RegisterAudioAPI(engine);
  269. RegisterUIAPI(engine);
  270. RegisterNetworkAPI(engine);
  271. RegisterPhysicsAPI(engine);
  272. RegisterScriptAPI(engine);
  273. RegisterEngineAPI(engine);
  274. }
  275. return true;
  276. }
  277. void Engine::RunFrame()
  278. {
  279. assert(initialized_ && !exiting_);
  280. Time* time = GetSubsystem<Time>();
  281. Input* input = GetSubsystem<Input>();
  282. Audio* audio = GetSubsystem<Audio>();
  283. time->BeginFrame(timeStep_);
  284. // If pause when minimized -mode is in use, stop updates and audio as necessary
  285. if (pauseMinimized_ && input->IsMinimized())
  286. {
  287. if (audio->IsPlaying())
  288. {
  289. audio->Stop();
  290. audioPaused_ = true;
  291. }
  292. }
  293. else
  294. {
  295. // Only unpause when it was paused by the engine
  296. if (audioPaused_)
  297. {
  298. audio->Play();
  299. audioPaused_ = false;
  300. }
  301. Update();
  302. }
  303. Render();
  304. ApplyFrameLimit();
  305. time->EndFrame();
  306. }
  307. Console* Engine::CreateConsole()
  308. {
  309. if (headless_ || !initialized_)
  310. return 0;
  311. context_->RegisterSubsystem(new Console(context_));
  312. return GetSubsystem<Console>();
  313. }
  314. DebugHud* Engine::CreateDebugHud()
  315. {
  316. if (headless_ || !initialized_)
  317. return 0;
  318. context_->RegisterSubsystem(new DebugHud(context_));
  319. return GetSubsystem<DebugHud>();
  320. }
  321. void Engine::SetMinFps(int fps)
  322. {
  323. minFps_ = Max(fps, 0);
  324. }
  325. void Engine::SetMaxFps(int fps)
  326. {
  327. maxFps_ = Max(fps, 0);
  328. }
  329. void Engine::SetMaxInactiveFps(int fps)
  330. {
  331. maxInactiveFps_ = Max(fps, 0);
  332. }
  333. void Engine::SetPauseMinimized(bool enable)
  334. {
  335. pauseMinimized_ = enable;
  336. }
  337. void Engine::Exit()
  338. {
  339. Graphics* graphics = GetSubsystem<Graphics>();
  340. if (graphics)
  341. graphics->Close();
  342. exiting_ = true;
  343. }
  344. void Engine::DumpProfilingData()
  345. {
  346. Profiler* profiler = GetSubsystem<Profiler>();
  347. if (profiler)
  348. LOGRAW(profiler->GetData(true, true) + "\n");
  349. }
  350. void Engine::DumpResources()
  351. {
  352. #ifdef ENABLE_LOGGING
  353. ResourceCache* cache = GetSubsystem<ResourceCache>();
  354. const HashMap<ShortStringHash, ResourceGroup>& resourceGroups = cache->GetAllResources();
  355. LOGRAW("\n");
  356. for (HashMap<ShortStringHash, ResourceGroup>::ConstIterator i = resourceGroups.Begin();
  357. i != resourceGroups.End(); ++i)
  358. {
  359. unsigned num = i->second_.resources_.Size();
  360. unsigned memoryUse = i->second_.memoryUse_;
  361. if (num)
  362. {
  363. LOGRAW("Resource type " + i->second_.resources_.Begin()->second_->GetTypeName() +
  364. ": count " + String(num) + " memory use " + String(memoryUse) + "\n");
  365. }
  366. }
  367. LOGRAW("Total memory use of all resources " + String(cache->GetTotalMemoryUse()) + "\n\n");
  368. #endif
  369. }
  370. void Engine::DumpMemory()
  371. {
  372. #ifdef ENABLE_LOGGING
  373. #if defined(_MSC_VER) && defined(_DEBUG)
  374. _CrtMemState state;
  375. _CrtMemCheckpoint(&state);
  376. _CrtMemBlockHeader* block = state.pBlockHeader;
  377. unsigned total = 0;
  378. unsigned blocks = 0;
  379. for (;;)
  380. {
  381. if (block && block->pBlockHeaderNext)
  382. block = block->pBlockHeaderNext;
  383. else
  384. break;
  385. }
  386. while (block)
  387. {
  388. if (block->nBlockUse > 0)
  389. {
  390. if (block->szFileName)
  391. LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes, file " + String(block->szFileName) + " line " + String(block->nLine) + "\n");
  392. else
  393. LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes\n");
  394. total += block->nDataSize;
  395. ++blocks;
  396. }
  397. block = block->pBlockHeaderPrev;
  398. }
  399. LOGRAW("Total allocated memory " + String(total) + " bytes in " + String(blocks) + " blocks\n\n");
  400. #else
  401. LOGRAW("DumpMemory() supported on MSVC debug mode only\n\n");
  402. #endif
  403. #endif
  404. }
  405. void Engine::Update()
  406. {
  407. PROFILE(Update);
  408. // Logic update event
  409. using namespace Update;
  410. VariantMap eventData;
  411. eventData[P_TIMESTEP] = timeStep_;
  412. SendEvent(E_UPDATE, eventData);
  413. // Logic post-update event
  414. SendEvent(E_POSTUPDATE, eventData);
  415. // Rendering update event
  416. SendEvent(E_RENDERUPDATE, eventData);
  417. // Post-render update event
  418. SendEvent(E_POSTRENDERUPDATE, eventData);
  419. }
  420. void Engine::Render()
  421. {
  422. PROFILE(Render);
  423. // Do not render if device lost
  424. Graphics* graphics = GetSubsystem<Graphics>();
  425. if (!graphics || !graphics->BeginFrame())
  426. return;
  427. GetSubsystem<Renderer>()->Render();
  428. GetSubsystem<UI>()->Render();
  429. graphics->EndFrame();
  430. }
  431. void Engine::ApplyFrameLimit()
  432. {
  433. if (!initialized_)
  434. return;
  435. int maxFps = maxFps_;
  436. Input* input = GetSubsystem<Input>();
  437. if (input && !input->IsActive())
  438. maxFps = Min(maxInactiveFps_, maxFps);
  439. long long elapsed = 0;
  440. // Perform waiting loop if maximum FPS set
  441. if (maxFps)
  442. {
  443. PROFILE(ApplyFrameLimit);
  444. long long targetMax = 1000000LL / maxFps;
  445. for (;;)
  446. {
  447. elapsed = frameTimer_.GetUSec(false);
  448. if (elapsed >= targetMax)
  449. break;
  450. // Sleep if 1 ms or more off the frame limiting goal
  451. if (targetMax - elapsed >= 1000LL)
  452. {
  453. unsigned sleepTime = (unsigned)((targetMax - elapsed) / 1000LL);
  454. Time::Sleep(sleepTime);
  455. }
  456. }
  457. }
  458. elapsed = frameTimer_.GetUSec(true);
  459. // If FPS lower than minimum, clamp elapsed time
  460. if (minFps_)
  461. {
  462. long long targetMin = 1000000LL / minFps_;
  463. if (elapsed > targetMin)
  464. elapsed = targetMin;
  465. }
  466. timeStep_ = elapsed / 1000000.0f;
  467. }
  468. void Engine::RegisterObjects()
  469. {
  470. RegisterResourceLibrary(context_);
  471. RegisterSceneLibrary(context_);
  472. RegisterNetworkLibrary(context_);
  473. RegisterGraphicsLibrary(context_);
  474. RegisterAudioLibrary(context_);
  475. RegisterUILibrary(context_);
  476. RegisterPhysicsLibrary(context_);
  477. }
  478. void Engine::RegisterSubsystems()
  479. {
  480. // Register self as a subsystem
  481. context_->RegisterSubsystem(this);
  482. // Create and register the rest of the subsystems
  483. context_->RegisterSubsystem(new Time(context_));
  484. context_->RegisterSubsystem(new WorkQueue(context_));
  485. #ifdef ENABLE_PROFILING
  486. context_->RegisterSubsystem(new Profiler(context_));
  487. #endif
  488. #ifdef ENABLE_LOGGING
  489. context_->RegisterSubsystem(new Log(context_));
  490. #endif
  491. context_->RegisterSubsystem(new FileSystem(context_));
  492. context_->RegisterSubsystem(new ResourceCache(context_));
  493. context_->RegisterSubsystem(new Network(context_));
  494. if (!headless_)
  495. {
  496. context_->RegisterSubsystem(new Graphics(context_));
  497. context_->RegisterSubsystem(new Renderer(context_));
  498. }
  499. context_->RegisterSubsystem(new Input(context_));
  500. context_->RegisterSubsystem(new UI(context_));
  501. context_->RegisterSubsystem(new Audio(context_));
  502. }
  503. }