Engine.cpp 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Audio/Audio.h"
  5. #include "../Core/Context.h"
  6. #include "../Core/CoreEvents.h"
  7. #include "../Core/EventProfiler.h"
  8. #include "../Core/ProcessUtils.h"
  9. #include "../Core/WorkQueue.h"
  10. #include "../Engine/Console.h"
  11. #include "../Engine/DebugHud.h"
  12. #include "../Engine/Engine.h"
  13. #include "../Engine/EngineDefs.h"
  14. #include "../Graphics/Graphics.h"
  15. #include "../Graphics/Renderer.h"
  16. #include "../Input/Input.h"
  17. #include "../IO/FileSystem.h"
  18. #include "../IO/Log.h"
  19. #include "../IO/PackageFile.h"
  20. #ifdef URHO3D_IK
  21. #include "../IK/IK.h"
  22. #endif
  23. #ifdef URHO3D_NAVIGATION
  24. #include "../Navigation/NavigationMesh.h"
  25. #endif
  26. #ifdef URHO3D_NETWORK
  27. #include "../Network/Network.h"
  28. #endif
  29. #ifdef URHO3D_DATABASE
  30. #include "../Database/Database.h"
  31. #endif
  32. #ifdef URHO3D_PHYSICS
  33. #include "../Physics/PhysicsWorld.h"
  34. #include "../Physics/RaycastVehicle.h"
  35. #endif
  36. #ifdef URHO3D_PHYSICS2D
  37. #include "../Physics2D/Physics2D.h"
  38. #endif
  39. #include "../Resource/ResourceCache.h"
  40. #include "../Resource/Localization.h"
  41. #include "../Scene/Scene.h"
  42. #include "../Scene/SceneEvents.h"
  43. #include "../UI/UI.h"
  44. #ifdef URHO3D_URHO2D
  45. #include "../Urho2D/Urho2D.h"
  46. #endif
  47. #if defined(__EMSCRIPTEN__) && defined(URHO3D_TESTING)
  48. #include <emscripten/emscripten.h>
  49. #endif
  50. #include "../DebugNew.h"
  51. #if defined(_MSC_VER) && defined(_DEBUG)
  52. // From dbgint.h
  53. #define nNoMansLandSize 4
  54. typedef struct _CrtMemBlockHeader
  55. {
  56. struct _CrtMemBlockHeader* pBlockHeaderNext;
  57. struct _CrtMemBlockHeader* pBlockHeaderPrev;
  58. char* szFileName;
  59. int nLine;
  60. size_t nDataSize;
  61. int nBlockUse;
  62. long lRequest;
  63. unsigned char gap[nNoMansLandSize];
  64. } _CrtMemBlockHeader;
  65. #endif
  66. namespace Urho3D
  67. {
  68. extern const char* logLevelPrefixes[];
  69. Engine::Engine(Context* context) :
  70. Object(context),
  71. timeStep_(0.0f),
  72. timeStepSmoothing_(2),
  73. minFps_(10),
  74. #if defined(IOS) || defined(TVOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
  75. maxFps_(60),
  76. maxInactiveFps_(10),
  77. pauseMinimized_(true),
  78. #else
  79. maxFps_(200),
  80. maxInactiveFps_(60),
  81. pauseMinimized_(false),
  82. #endif
  83. #ifdef URHO3D_TESTING
  84. timeOut_(0),
  85. #endif
  86. autoExit_(true),
  87. initialized_(false),
  88. exiting_(false),
  89. headless_(false),
  90. audioPaused_(false)
  91. {
  92. // Register self as a subsystem
  93. context_->RegisterSubsystem(this);
  94. // Create subsystems which do not depend on engine initialization or startup parameters
  95. context_->RegisterSubsystem(new Time(context_));
  96. context_->RegisterSubsystem(new WorkQueue(context_));
  97. #ifdef URHO3D_PROFILING
  98. context_->RegisterSubsystem(new Profiler(context_));
  99. #endif
  100. context_->RegisterSubsystem(new FileSystem(context_));
  101. #ifdef URHO3D_LOGGING
  102. context_->RegisterSubsystem(new Log(context_));
  103. #endif
  104. context_->RegisterSubsystem(new ResourceCache(context_));
  105. context_->RegisterSubsystem(new Localization(context_));
  106. #ifdef URHO3D_NETWORK
  107. context_->RegisterSubsystem(new Network(context_));
  108. #endif
  109. #ifdef URHO3D_DATABASE
  110. context_->RegisterSubsystem(new Database(context_));
  111. #endif
  112. context_->RegisterSubsystem(new Input(context_));
  113. context_->RegisterSubsystem(new Audio(context_));
  114. context_->RegisterSubsystem(new UI(context_));
  115. // Register object factories for libraries which are not automatically registered along with subsystem creation
  116. RegisterSceneLibrary(context_);
  117. #ifdef URHO3D_IK
  118. RegisterIKLibrary(context_);
  119. #endif
  120. #ifdef URHO3D_PHYSICS
  121. RegisterPhysicsLibrary(context_);
  122. #endif
  123. #ifdef URHO3D_PHYSICS2D
  124. RegisterPhysics2DLibrary(context_);
  125. #endif
  126. #ifdef URHO3D_NAVIGATION
  127. RegisterNavigationLibrary(context_);
  128. #endif
  129. SubscribeToEvent(E_EXITREQUESTED, URHO3D_HANDLER(Engine, HandleExitRequested));
  130. }
  131. Engine::~Engine() = default;
  132. bool Engine::Initialize(const VariantMap& parameters)
  133. {
  134. if (initialized_)
  135. return true;
  136. URHO3D_PROFILE(InitEngine);
  137. // Set headless mode
  138. headless_ = GetParameter(parameters, EP_HEADLESS, false).GetBool();
  139. // Register the rest of the subsystems
  140. if (!headless_)
  141. {
  142. GAPI gapi = GAPI_NONE;
  143. // Try to set any possible graphics API as default
  144. #ifdef URHO3D_OPENGL
  145. gapi = GAPI_OPENGL;
  146. #endif
  147. #ifdef URHO3D_D3D9
  148. gapi = GAPI_D3D9;
  149. #endif
  150. #ifdef URHO3D_D3D11
  151. gapi = GAPI_D3D11;
  152. #endif
  153. // Use command line parameters
  154. #ifdef URHO3D_OPENGL
  155. bool gapi_gl = GetParameter(parameters, EP_OPENGL, false).GetBool();
  156. if (gapi_gl)
  157. gapi = GAPI_OPENGL;
  158. #endif
  159. #ifdef URHO3D_D3D9
  160. bool gapi_d3d9 = GetParameter(parameters, EP_DIRECT3D9, false).GetBool();
  161. if (gapi_d3d9)
  162. gapi = GAPI_D3D9;
  163. #endif
  164. #ifdef URHO3D_D3D11
  165. bool gapi_d3d11 = GetParameter(parameters, EP_DIRECT3D11, false).GetBool();
  166. if (gapi_d3d11)
  167. gapi = GAPI_D3D11;
  168. #endif
  169. if (gapi == GAPI_NONE)
  170. {
  171. URHO3D_LOGERROR("Graphics API not selected");
  172. return false;
  173. }
  174. context_->RegisterSubsystem(new Graphics(context_, gapi));
  175. context_->RegisterSubsystem(new Renderer(context_));
  176. }
  177. else
  178. {
  179. // Register graphics library objects explicitly in headless mode to allow them to work without using actual GPU resources
  180. RegisterGraphicsLibrary(context_);
  181. }
  182. #ifdef URHO3D_URHO2D
  183. // 2D graphics library is dependent on 3D graphics library
  184. RegisterUrho2DLibrary(context_);
  185. #endif
  186. // Start logging
  187. auto* log = GetSubsystem<Log>();
  188. if (log)
  189. {
  190. if (HasParameter(parameters, EP_LOG_LEVEL))
  191. log->SetLevel(GetParameter(parameters, EP_LOG_LEVEL).GetInt());
  192. log->SetQuiet(GetParameter(parameters, EP_LOG_QUIET, false).GetBool());
  193. log->Open(GetParameter(parameters, EP_LOG_NAME, "Urho3D.log").GetString());
  194. }
  195. // Set maximally accurate low res timer
  196. GetSubsystem<Time>()->SetTimerPeriod(1);
  197. // Configure max FPS
  198. if (GetParameter(parameters, EP_FRAME_LIMITER, true) == false)
  199. SetMaxFps(0);
  200. // Set amount of worker threads according to the available physical CPU cores. Using also hyperthreaded cores results in
  201. // unpredictable extra synchronization overhead. Also reserve one core for the main thread
  202. #ifdef URHO3D_THREADING
  203. unsigned numThreads = GetParameter(parameters, EP_WORKER_THREADS, true).GetBool() ? GetNumPhysicalCPUs() - 1 : 0;
  204. if (numThreads)
  205. {
  206. GetSubsystem<WorkQueue>()->CreateThreads(numThreads);
  207. URHO3D_LOGINFOF("Created %u worker thread%s", numThreads, numThreads > 1 ? "s" : "");
  208. }
  209. #endif
  210. // Add resource paths
  211. if (!InitializeResourceCache(parameters, false))
  212. return false;
  213. auto* cache = GetSubsystem<ResourceCache>();
  214. auto* fileSystem = GetSubsystem<FileSystem>();
  215. // Initialize graphics & audio output
  216. if (!headless_)
  217. {
  218. auto* graphics = GetSubsystem<Graphics>();
  219. auto* renderer = GetSubsystem<Renderer>();
  220. if (HasParameter(parameters, EP_EXTERNAL_WINDOW))
  221. graphics->SetExternalWindow(GetParameter(parameters, EP_EXTERNAL_WINDOW).GetVoidPtr());
  222. graphics->SetWindowTitle(GetParameter(parameters, EP_WINDOW_TITLE, "Urho3D").GetString());
  223. graphics->SetWindowIcon(cache->GetResource<Image>(GetParameter(parameters, EP_WINDOW_ICON, String::EMPTY).GetString()));
  224. graphics->SetFlushGPU(GetParameter(parameters, EP_FLUSH_GPU, false).GetBool());
  225. graphics->SetOrientations(GetParameter(parameters, EP_ORIENTATIONS, "LandscapeLeft LandscapeRight").GetString());
  226. if (HasParameter(parameters, EP_WINDOW_POSITION_X) && HasParameter(parameters, EP_WINDOW_POSITION_Y))
  227. graphics->SetWindowPosition(GetParameter(parameters, EP_WINDOW_POSITION_X).GetInt(),
  228. GetParameter(parameters, EP_WINDOW_POSITION_Y).GetInt());
  229. if (Graphics::GetGAPI() == GAPI_OPENGL)
  230. {
  231. if (HasParameter(parameters, EP_FORCE_GL2))
  232. graphics->SetForceGL2(GetParameter(parameters, EP_FORCE_GL2).GetBool());
  233. }
  234. if (!graphics->SetMode(
  235. GetParameter(parameters, EP_WINDOW_WIDTH, 0).GetInt(),
  236. GetParameter(parameters, EP_WINDOW_HEIGHT, 0).GetInt(),
  237. GetParameter(parameters, EP_FULL_SCREEN, true).GetBool(),
  238. GetParameter(parameters, EP_BORDERLESS, false).GetBool(),
  239. GetParameter(parameters, EP_WINDOW_RESIZABLE, false).GetBool(),
  240. GetParameter(parameters, EP_HIGH_DPI, true).GetBool(),
  241. GetParameter(parameters, EP_VSYNC, false).GetBool(),
  242. GetParameter(parameters, EP_TRIPLE_BUFFER, false).GetBool(),
  243. GetParameter(parameters, EP_MULTI_SAMPLE, 1).GetInt(),
  244. GetParameter(parameters, EP_MONITOR, 0).GetInt(),
  245. GetParameter(parameters, EP_REFRESH_RATE, 0).GetInt()
  246. ))
  247. return false;
  248. graphics->SetShaderCacheDir(GetParameter(parameters, EP_SHADER_CACHE_DIR, fileSystem->GetAppPreferencesDir("urho3d", "shadercache")).GetString());
  249. if (HasParameter(parameters, EP_DUMP_SHADERS))
  250. graphics->BeginDumpShaders(GetParameter(parameters, EP_DUMP_SHADERS, String::EMPTY).GetString());
  251. if (HasParameter(parameters, EP_RENDER_PATH))
  252. renderer->SetDefaultRenderPath(cache->GetResource<XMLFile>(GetParameter(parameters, EP_RENDER_PATH).GetString()));
  253. renderer->SetDrawShadows(GetParameter(parameters, EP_SHADOWS, true).GetBool());
  254. if (renderer->GetDrawShadows() && GetParameter(parameters, EP_LOW_QUALITY_SHADOWS, false).GetBool())
  255. renderer->SetShadowQuality(SHADOWQUALITY_SIMPLE_16BIT);
  256. renderer->SetMaterialQuality((MaterialQuality)GetParameter(parameters, EP_MATERIAL_QUALITY, QUALITY_HIGH).GetInt());
  257. renderer->SetTextureQuality((MaterialQuality)GetParameter(parameters, EP_TEXTURE_QUALITY, QUALITY_HIGH).GetInt());
  258. renderer->SetTextureFilterMode((TextureFilterMode)GetParameter(parameters, EP_TEXTURE_FILTER_MODE, FILTER_TRILINEAR).GetInt());
  259. renderer->SetTextureAnisotropy(GetParameter(parameters, EP_TEXTURE_ANISOTROPY, 4).GetInt());
  260. if (GetParameter(parameters, EP_SOUND, true).GetBool())
  261. {
  262. GetSubsystem<Audio>()->SetMode(
  263. GetParameter(parameters, EP_SOUND_BUFFER, 100).GetInt(),
  264. GetParameter(parameters, EP_SOUND_MIX_RATE, 44100).GetInt(),
  265. GetParameter(parameters, EP_SOUND_STEREO, true).GetBool(),
  266. GetParameter(parameters, EP_SOUND_INTERPOLATION, true).GetBool()
  267. );
  268. }
  269. }
  270. // Init FPU state of main thread
  271. InitFPU();
  272. // Initialize input
  273. if (HasParameter(parameters, EP_TOUCH_EMULATION))
  274. GetSubsystem<Input>()->SetTouchEmulation(GetParameter(parameters, EP_TOUCH_EMULATION).GetBool());
  275. // Initialize network
  276. #ifdef URHO3D_NETWORK
  277. if (HasParameter(parameters, EP_PACKAGE_CACHE_DIR))
  278. GetSubsystem<Network>()->SetPackageCacheDir(GetParameter(parameters, EP_PACKAGE_CACHE_DIR).GetString());
  279. #endif
  280. #ifdef URHO3D_TESTING
  281. if (HasParameter(parameters, EP_TIME_OUT))
  282. timeOut_ = GetParameter(parameters, EP_TIME_OUT, 0).GetInt() * 1000000LL;
  283. #endif
  284. #ifdef URHO3D_PROFILING
  285. if (GetParameter(parameters, EP_EVENT_PROFILER, true).GetBool())
  286. {
  287. context_->RegisterSubsystem(new EventProfiler(context_));
  288. EventProfiler::SetActive(true);
  289. }
  290. #endif
  291. frameTimer_.Reset();
  292. URHO3D_LOGINFO("Initialized engine");
  293. initialized_ = true;
  294. return true;
  295. }
  296. bool Engine::InitializeResourceCache(const VariantMap& parameters, bool removeOld /*= true*/)
  297. {
  298. auto* cache = GetSubsystem<ResourceCache>();
  299. auto* fileSystem = GetSubsystem<FileSystem>();
  300. // Remove all resource paths and packages
  301. if (removeOld)
  302. {
  303. Vector<String> resourceDirs = cache->GetResourceDirs();
  304. Vector<SharedPtr<PackageFile>> packageFiles = cache->GetPackageFiles();
  305. for (unsigned i = 0; i < resourceDirs.Size(); ++i)
  306. cache->RemoveResourceDir(resourceDirs[i]);
  307. for (unsigned i = 0; i < packageFiles.Size(); ++i)
  308. cache->RemovePackageFile(packageFiles[i]);
  309. }
  310. // Add resource paths
  311. Vector<String> resourcePrefixPaths = GetParameter(parameters, EP_RESOURCE_PREFIX_PATHS, String::EMPTY).GetString().Split(';', true);
  312. for (unsigned i = 0; i < resourcePrefixPaths.Size(); ++i)
  313. resourcePrefixPaths[i] = AddTrailingSlash(
  314. IsAbsolutePath(resourcePrefixPaths[i]) ? resourcePrefixPaths[i] : fileSystem->GetProgramDir() + resourcePrefixPaths[i]);
  315. Vector<String> resourcePaths = GetParameter(parameters, EP_RESOURCE_PATHS, "Data;CoreData").GetString().Split(';');
  316. Vector<String> resourcePackages = GetParameter(parameters, EP_RESOURCE_PACKAGES).GetString().Split(';');
  317. Vector<String> autoLoadPaths = GetParameter(parameters, EP_AUTOLOAD_PATHS, "Autoload").GetString().Split(';');
  318. for (unsigned i = 0; i < resourcePaths.Size(); ++i)
  319. {
  320. // If path is not absolute, prefer to add it as a package if possible
  321. if (!IsAbsolutePath(resourcePaths[i]))
  322. {
  323. unsigned j = 0;
  324. for (; j < resourcePrefixPaths.Size(); ++j)
  325. {
  326. String packageName = resourcePrefixPaths[j] + resourcePaths[i] + ".pak";
  327. if (fileSystem->FileExists(packageName))
  328. {
  329. if (cache->AddPackageFile(packageName))
  330. break;
  331. else
  332. return false; // The root cause of the error should have already been logged
  333. }
  334. String pathName = resourcePrefixPaths[j] + resourcePaths[i];
  335. if (fileSystem->DirExists(pathName))
  336. {
  337. if (cache->AddResourceDir(pathName))
  338. break;
  339. else
  340. return false;
  341. }
  342. }
  343. if (j == resourcePrefixPaths.Size())
  344. {
  345. URHO3D_LOGERRORF(
  346. "Failed to add resource path '%s', check the documentation on how to set the 'resource prefix path'",
  347. resourcePaths[i].CString());
  348. return false;
  349. }
  350. }
  351. else
  352. {
  353. String pathName = resourcePaths[i];
  354. if (fileSystem->DirExists(pathName))
  355. if (!cache->AddResourceDir(pathName))
  356. return false;
  357. }
  358. }
  359. // Then add specified packages
  360. for (unsigned i = 0; i < resourcePackages.Size(); ++i)
  361. {
  362. unsigned j = 0;
  363. for (; j < resourcePrefixPaths.Size(); ++j)
  364. {
  365. String packageName = resourcePrefixPaths[j] + resourcePackages[i];
  366. if (fileSystem->FileExists(packageName))
  367. {
  368. if (cache->AddPackageFile(packageName))
  369. break;
  370. else
  371. return false;
  372. }
  373. }
  374. if (j == resourcePrefixPaths.Size())
  375. {
  376. URHO3D_LOGERRORF(
  377. "Failed to add resource package '%s', check the documentation on how to set the 'resource prefix path'",
  378. resourcePackages[i].CString());
  379. return false;
  380. }
  381. }
  382. // Add auto load folders. Prioritize these (if exist) before the default folders
  383. for (unsigned i = 0; i < autoLoadPaths.Size(); ++i)
  384. {
  385. bool autoLoadPathExist = false;
  386. for (unsigned j = 0; j < resourcePrefixPaths.Size(); ++j)
  387. {
  388. String autoLoadPath(autoLoadPaths[i]);
  389. if (!IsAbsolutePath(autoLoadPath))
  390. autoLoadPath = resourcePrefixPaths[j] + autoLoadPath;
  391. if (fileSystem->DirExists(autoLoadPath))
  392. {
  393. autoLoadPathExist = true;
  394. // Add all the subdirs (non-recursive) as resource directory
  395. Vector<String> subdirs;
  396. fileSystem->ScanDir(subdirs, autoLoadPath, "*", SCAN_DIRS, false);
  397. for (unsigned y = 0; y < subdirs.Size(); ++y)
  398. {
  399. String dir = subdirs[y];
  400. if (dir.StartsWith("."))
  401. continue;
  402. String autoResourceDir = autoLoadPath + "/" + dir;
  403. if (!cache->AddResourceDir(autoResourceDir, 0))
  404. return false;
  405. }
  406. // Add all the found package files (non-recursive)
  407. Vector<String> paks;
  408. fileSystem->ScanDir(paks, autoLoadPath, "*.pak", SCAN_FILES, false);
  409. for (unsigned y = 0; y < paks.Size(); ++y)
  410. {
  411. String pak = paks[y];
  412. if (pak.StartsWith("."))
  413. continue;
  414. String autoPackageName = autoLoadPath + "/" + pak;
  415. if (!cache->AddPackageFile(autoPackageName, 0))
  416. return false;
  417. }
  418. }
  419. }
  420. // The following debug message is confusing when user is not aware of the autoload feature
  421. // Especially because the autoload feature is enabled by default without user intervention
  422. // The following extra conditional check below is to suppress unnecessary debug log entry under such default situation
  423. // The cleaner approach is to not enable the autoload by default, i.e. do not use 'Autoload' as default value for 'AutoloadPaths' engine parameter
  424. // However, doing so will break the existing applications that rely on this
  425. if (!autoLoadPathExist && (autoLoadPaths.Size() > 1 || autoLoadPaths[0] != "Autoload"))
  426. URHO3D_LOGDEBUGF(
  427. "Skipped autoload path '%s' as it does not exist, check the documentation on how to set the 'resource prefix path'",
  428. autoLoadPaths[i].CString());
  429. }
  430. return true;
  431. }
  432. void Engine::RunFrame()
  433. {
  434. assert(initialized_);
  435. // If not headless, and the graphics subsystem no longer has a window open, assume we should exit
  436. if (!headless_ && !GetSubsystem<Graphics>()->IsInitialized())
  437. exiting_ = true;
  438. if (exiting_)
  439. return;
  440. // Note: there is a minimal performance cost to looking up subsystems (uses a hashmap); if they would be looked up several
  441. // times per frame it would be better to cache the pointers
  442. auto* time = GetSubsystem<Time>();
  443. auto* input = GetSubsystem<Input>();
  444. auto* audio = GetSubsystem<Audio>();
  445. #ifdef URHO3D_PROFILING
  446. if (EventProfiler::IsActive())
  447. {
  448. auto* eventProfiler = GetSubsystem<EventProfiler>();
  449. if (eventProfiler)
  450. eventProfiler->BeginFrame();
  451. }
  452. #endif
  453. time->BeginFrame(timeStep_);
  454. // If pause when minimized -mode is in use, stop updates and audio as necessary
  455. if (pauseMinimized_ && input->IsMinimized())
  456. {
  457. if (audio->IsPlaying())
  458. {
  459. audio->Stop();
  460. audioPaused_ = true;
  461. }
  462. }
  463. else
  464. {
  465. // Only unpause when it was paused by the engine
  466. if (audioPaused_)
  467. {
  468. audio->Play();
  469. audioPaused_ = false;
  470. }
  471. Update();
  472. }
  473. Render();
  474. ApplyFrameLimit();
  475. time->EndFrame();
  476. // Mark a frame for profiling
  477. URHO3D_PROFILE_FRAME();
  478. }
  479. Console* Engine::CreateConsole()
  480. {
  481. if (headless_ || !initialized_)
  482. return nullptr;
  483. // Return existing console if possible
  484. auto* console = GetSubsystem<Console>();
  485. if (!console)
  486. {
  487. console = new Console(context_);
  488. context_->RegisterSubsystem(console);
  489. }
  490. return console;
  491. }
  492. DebugHud* Engine::CreateDebugHud()
  493. {
  494. if (headless_ || !initialized_)
  495. return nullptr;
  496. // Return existing debug HUD if possible
  497. auto* debugHud = GetSubsystem<DebugHud>();
  498. if (!debugHud)
  499. {
  500. debugHud = new DebugHud(context_);
  501. context_->RegisterSubsystem(debugHud);
  502. }
  503. return debugHud;
  504. }
  505. void Engine::SetTimeStepSmoothing(int frames)
  506. {
  507. timeStepSmoothing_ = (unsigned)Clamp(frames, 1, 20);
  508. }
  509. void Engine::SetMinFps(int fps)
  510. {
  511. minFps_ = (unsigned)Max(fps, 0);
  512. }
  513. void Engine::SetMaxFps(int fps)
  514. {
  515. maxFps_ = (unsigned)Max(fps, 0);
  516. }
  517. void Engine::SetMaxInactiveFps(int fps)
  518. {
  519. maxInactiveFps_ = (unsigned)Max(fps, 0);
  520. }
  521. void Engine::SetPauseMinimized(bool enable)
  522. {
  523. pauseMinimized_ = enable;
  524. }
  525. void Engine::SetAutoExit(bool enable)
  526. {
  527. // On mobile platforms exit is mandatory if requested by the platform itself and should not be attempted to be disabled
  528. #if defined(__ANDROID__) || defined(IOS) || defined(TVOS)
  529. enable = true;
  530. #endif
  531. autoExit_ = enable;
  532. }
  533. void Engine::SetNextTimeStep(float seconds)
  534. {
  535. timeStep_ = Max(seconds, 0.0f);
  536. }
  537. void Engine::Exit()
  538. {
  539. #if defined(IOS) || defined(TVOS)
  540. // On iOS/tvOS it's not legal for the application to exit on its own, instead it will be minimized with the home key
  541. #else
  542. DoExit();
  543. #endif
  544. }
  545. void Engine::DumpProfiler()
  546. {
  547. #ifdef URHO3D_LOGGING
  548. if (!Thread::IsMainThread())
  549. return;
  550. auto* profiler = GetSubsystem<Profiler>();
  551. if (profiler)
  552. URHO3D_LOGRAW(profiler->PrintData(true, true) + "\n");
  553. #endif
  554. }
  555. void Engine::DumpResources(bool dumpFileName)
  556. {
  557. #ifdef URHO3D_LOGGING
  558. if (!Thread::IsMainThread())
  559. return;
  560. auto* cache = GetSubsystem<ResourceCache>();
  561. const HashMap<StringHash, ResourceGroup>& resourceGroups = cache->GetAllResources();
  562. if (dumpFileName)
  563. {
  564. URHO3D_LOGRAW("Used resources:\n");
  565. for (HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups.Begin(); i != resourceGroups.End(); ++i)
  566. {
  567. const HashMap<StringHash, SharedPtr<Resource>>& resources = i->second_.resources_;
  568. if (dumpFileName)
  569. {
  570. for (HashMap<StringHash, SharedPtr<Resource>>::ConstIterator j = resources.Begin(); j != resources.End(); ++j)
  571. URHO3D_LOGRAW(j->second_->GetName() + "\n");
  572. }
  573. }
  574. }
  575. else
  576. URHO3D_LOGRAW(cache->PrintMemoryUsage() + "\n");
  577. #endif
  578. }
  579. void Engine::DumpMemory()
  580. {
  581. #ifdef URHO3D_LOGGING
  582. #if defined(_MSC_VER) && defined(_DEBUG)
  583. _CrtMemState state;
  584. _CrtMemCheckpoint(&state);
  585. _CrtMemBlockHeader* block = state.pBlockHeader;
  586. unsigned total = 0;
  587. unsigned blocks = 0;
  588. for (;;)
  589. {
  590. if (block && block->pBlockHeaderNext)
  591. block = block->pBlockHeaderNext;
  592. else
  593. break;
  594. }
  595. while (block)
  596. {
  597. if (block->nBlockUse > 0)
  598. {
  599. if (block->szFileName)
  600. URHO3D_LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes, file " + String(block->szFileName) + " line " + String(block->nLine) + "\n");
  601. else
  602. URHO3D_LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes\n");
  603. total += block->nDataSize;
  604. ++blocks;
  605. }
  606. block = block->pBlockHeaderPrev;
  607. }
  608. URHO3D_LOGRAW("Total allocated memory " + String(total) + " bytes in " + String(blocks) + " blocks\n\n");
  609. #else
  610. URHO3D_LOGRAW("DumpMemory() supported on MSVC debug mode only\n\n");
  611. #endif
  612. #endif
  613. }
  614. void Engine::Update()
  615. {
  616. URHO3D_PROFILE(Update);
  617. // Logic update event
  618. using namespace Update;
  619. VariantMap& eventData = GetEventDataMap();
  620. eventData[P_TIMESTEP] = timeStep_;
  621. SendEvent(E_UPDATE, eventData);
  622. // Logic post-update event
  623. SendEvent(E_POSTUPDATE, eventData);
  624. // Rendering update event
  625. SendEvent(E_RENDERUPDATE, eventData);
  626. // Post-render update event
  627. SendEvent(E_POSTRENDERUPDATE, eventData);
  628. }
  629. void Engine::Render()
  630. {
  631. if (headless_)
  632. return;
  633. URHO3D_PROFILE(Render);
  634. // If device is lost, BeginFrame will fail and we skip rendering
  635. auto* graphics = GetSubsystem<Graphics>();
  636. if (!graphics->BeginFrame())
  637. return;
  638. GetSubsystem<Renderer>()->Render();
  639. GetSubsystem<UI>()->Render();
  640. graphics->EndFrame();
  641. }
  642. void Engine::ApplyFrameLimit()
  643. {
  644. if (!initialized_)
  645. return;
  646. unsigned maxFps = maxFps_;
  647. auto* input = GetSubsystem<Input>();
  648. if (input && !input->HasFocus())
  649. maxFps = Min(maxInactiveFps_, maxFps);
  650. long long elapsed = 0;
  651. #ifndef __EMSCRIPTEN__
  652. // Perform waiting loop if maximum FPS set
  653. #if !defined(IOS) && !defined(TVOS)
  654. if (maxFps)
  655. #else
  656. // If on iOS/tvOS and target framerate is 60 or above, just let the animation callback handle frame timing
  657. // instead of waiting ourselves
  658. if (maxFps < 60)
  659. #endif
  660. {
  661. URHO3D_PROFILE(ApplyFrameLimit);
  662. long long targetMax = 1000000LL / maxFps;
  663. for (;;)
  664. {
  665. elapsed = frameTimer_.GetUSec(false);
  666. if (elapsed >= targetMax)
  667. break;
  668. // Sleep if 1 ms or more off the frame limiting goal
  669. if (targetMax - elapsed >= 1000LL)
  670. {
  671. auto sleepTime = (unsigned)((targetMax - elapsed) / 1000LL);
  672. Time::Sleep(sleepTime);
  673. }
  674. }
  675. }
  676. #endif
  677. elapsed = frameTimer_.GetUSec(true);
  678. #ifdef URHO3D_TESTING
  679. if (timeOut_ > 0)
  680. {
  681. timeOut_ -= elapsed;
  682. if (timeOut_ <= 0)
  683. Exit();
  684. }
  685. #endif
  686. // If FPS lower than minimum, clamp elapsed time
  687. if (minFps_)
  688. {
  689. long long targetMin = 1000000LL / minFps_;
  690. if (elapsed > targetMin)
  691. elapsed = targetMin;
  692. }
  693. // Perform timestep smoothing
  694. timeStep_ = 0.0f;
  695. lastTimeSteps_.Push(elapsed / 1000000.0f);
  696. if (lastTimeSteps_.Size() > timeStepSmoothing_)
  697. {
  698. // If the smoothing configuration was changed, ensure correct amount of samples
  699. lastTimeSteps_.Erase(0, lastTimeSteps_.Size() - timeStepSmoothing_);
  700. for (unsigned i = 0; i < lastTimeSteps_.Size(); ++i)
  701. timeStep_ += lastTimeSteps_[i];
  702. timeStep_ /= lastTimeSteps_.Size();
  703. }
  704. else
  705. timeStep_ = lastTimeSteps_.Back();
  706. }
  707. VariantMap Engine::ParseParameters(const Vector<String>& arguments)
  708. {
  709. VariantMap ret;
  710. // Pre-initialize the parameters with environment variable values when they are set
  711. if (const char* paths = getenv("URHO3D_PREFIX_PATH"))
  712. ret[EP_RESOURCE_PREFIX_PATHS] = paths;
  713. for (unsigned i = 0; i < arguments.Size(); ++i)
  714. {
  715. if (arguments[i].Length() > 1 && arguments[i][0] == '-')
  716. {
  717. String argument = arguments[i].Substring(1).ToLower();
  718. String value = i + 1 < arguments.Size() ? arguments[i + 1] : String::EMPTY;
  719. if (argument == "headless")
  720. ret[EP_HEADLESS] = true;
  721. else if (argument == "nolimit")
  722. ret[EP_FRAME_LIMITER] = false;
  723. else if (argument == "flushgpu")
  724. ret[EP_FLUSH_GPU] = true;
  725. else if (argument == "opengl")
  726. ret[EP_OPENGL] = true;
  727. else if (argument == "d3d9")
  728. ret[EP_DIRECT3D9] = true;
  729. else if (argument == "d3d11")
  730. ret[EP_DIRECT3D11] = true;
  731. else if (argument == "gl2")
  732. ret[EP_FORCE_GL2] = true;
  733. else if (argument == "landscape")
  734. ret[EP_ORIENTATIONS] = "LandscapeLeft LandscapeRight " + ret[EP_ORIENTATIONS].GetString();
  735. else if (argument == "portrait")
  736. ret[EP_ORIENTATIONS] = "Portrait PortraitUpsideDown " + ret[EP_ORIENTATIONS].GetString();
  737. else if (argument == "nosound")
  738. ret[EP_SOUND] = false;
  739. else if (argument == "noip")
  740. ret[EP_SOUND_INTERPOLATION] = false;
  741. else if (argument == "mono")
  742. ret[EP_SOUND_STEREO] = false;
  743. else if (argument == "prepass")
  744. ret[EP_RENDER_PATH] = "RenderPaths/Prepass.xml";
  745. else if (argument == "deferred")
  746. ret[EP_RENDER_PATH] = "RenderPaths/Deferred.xml";
  747. else if (argument == "renderpath" && !value.Empty())
  748. {
  749. ret[EP_RENDER_PATH] = value;
  750. ++i;
  751. }
  752. else if (argument == "noshadows")
  753. ret[EP_SHADOWS] = false;
  754. else if (argument == "lqshadows")
  755. ret[EP_LOW_QUALITY_SHADOWS] = true;
  756. else if (argument == "nothreads")
  757. ret[EP_WORKER_THREADS] = false;
  758. else if (argument == "v")
  759. ret[EP_VSYNC] = true;
  760. else if (argument == "t")
  761. ret[EP_TRIPLE_BUFFER] = true;
  762. else if (argument == "w")
  763. ret[EP_FULL_SCREEN] = false;
  764. else if (argument == "borderless")
  765. ret[EP_BORDERLESS] = true;
  766. else if (argument == "lowdpi")
  767. ret[EP_HIGH_DPI] = false;
  768. else if (argument == "s")
  769. ret[EP_WINDOW_RESIZABLE] = true;
  770. else if (argument == "q")
  771. ret[EP_LOG_QUIET] = true;
  772. else if (argument == "log" && !value.Empty())
  773. {
  774. unsigned logLevel = GetStringListIndex(value.CString(), logLevelPrefixes, M_MAX_UNSIGNED);
  775. if (logLevel != M_MAX_UNSIGNED)
  776. {
  777. ret[EP_LOG_LEVEL] = logLevel;
  778. ++i;
  779. }
  780. }
  781. else if (argument == "x" && !value.Empty())
  782. {
  783. ret[EP_WINDOW_WIDTH] = ToInt(value);
  784. ++i;
  785. }
  786. else if (argument == "y" && !value.Empty())
  787. {
  788. ret[EP_WINDOW_HEIGHT] = ToInt(value);
  789. ++i;
  790. }
  791. else if (argument == "monitor" && !value.Empty()) {
  792. ret[EP_MONITOR] = ToInt(value);
  793. ++i;
  794. }
  795. else if (argument == "hz" && !value.Empty()) {
  796. ret[EP_REFRESH_RATE] = ToInt(value);
  797. ++i;
  798. }
  799. else if (argument == "m" && !value.Empty())
  800. {
  801. ret[EP_MULTI_SAMPLE] = ToInt(value);
  802. ++i;
  803. }
  804. else if (argument == "b" && !value.Empty())
  805. {
  806. ret[EP_SOUND_BUFFER] = ToInt(value);
  807. ++i;
  808. }
  809. else if (argument == "r" && !value.Empty())
  810. {
  811. ret[EP_SOUND_MIX_RATE] = ToInt(value);
  812. ++i;
  813. }
  814. else if (argument == "pp" && !value.Empty())
  815. {
  816. ret[EP_RESOURCE_PREFIX_PATHS] = value;
  817. ++i;
  818. }
  819. else if (argument == "p" && !value.Empty())
  820. {
  821. ret[EP_RESOURCE_PATHS] = value;
  822. ++i;
  823. }
  824. else if (argument == "pf" && !value.Empty())
  825. {
  826. ret[EP_RESOURCE_PACKAGES] = value;
  827. ++i;
  828. }
  829. else if (argument == "ap" && !value.Empty())
  830. {
  831. ret[EP_AUTOLOAD_PATHS] = value;
  832. ++i;
  833. }
  834. else if (argument == "ds" && !value.Empty())
  835. {
  836. ret[EP_DUMP_SHADERS] = value;
  837. ++i;
  838. }
  839. else if (argument == "mq" && !value.Empty())
  840. {
  841. ret[EP_MATERIAL_QUALITY] = ToInt(value);
  842. ++i;
  843. }
  844. else if (argument == "tq" && !value.Empty())
  845. {
  846. ret[EP_TEXTURE_QUALITY] = ToInt(value);
  847. ++i;
  848. }
  849. else if (argument == "tf" && !value.Empty())
  850. {
  851. ret[EP_TEXTURE_FILTER_MODE] = ToInt(value);
  852. ++i;
  853. }
  854. else if (argument == "af" && !value.Empty())
  855. {
  856. ret[EP_TEXTURE_FILTER_MODE] = FILTER_ANISOTROPIC;
  857. ret[EP_TEXTURE_ANISOTROPY] = ToInt(value);
  858. ++i;
  859. }
  860. else if (argument == "touch")
  861. ret[EP_TOUCH_EMULATION] = true;
  862. #ifdef URHO3D_TESTING
  863. else if (argument == "timeout" && !value.Empty())
  864. {
  865. ret[EP_TIME_OUT] = ToInt(value);
  866. ++i;
  867. }
  868. #endif
  869. }
  870. }
  871. return ret;
  872. }
  873. bool Engine::HasParameter(const VariantMap& parameters, const String& parameter)
  874. {
  875. StringHash nameHash(parameter);
  876. return parameters.Find(nameHash) != parameters.End();
  877. }
  878. const Variant& Engine::GetParameter(const VariantMap& parameters, const String& parameter, const Variant& defaultValue)
  879. {
  880. StringHash nameHash(parameter);
  881. VariantMap::ConstIterator i = parameters.Find(nameHash);
  882. return i != parameters.End() ? i->second_ : defaultValue;
  883. }
  884. void Engine::HandleExitRequested(StringHash eventType, VariantMap& eventData)
  885. {
  886. if (autoExit_)
  887. {
  888. // Do not call Exit() here, as it contains mobile platform -specific tests to not exit.
  889. // If we do receive an exit request from the system on those platforms, we must comply
  890. DoExit();
  891. }
  892. }
  893. void Engine::DoExit()
  894. {
  895. auto* graphics = GetSubsystem<Graphics>();
  896. if (graphics)
  897. graphics->Close();
  898. exiting_ = true;
  899. #if defined(__EMSCRIPTEN__) && defined(URHO3D_TESTING)
  900. emscripten_force_exit(EXIT_SUCCESS); // Some how this is required to signal emrun to stop
  901. #endif
  902. }
  903. }