main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. // This file is part of the "Irrlicht Engine".
  2. // For conditions of distribution and use, see copyright notice in irrlicht.h
  3. // Copyright by Michael Zeilfelder
  4. /** Example 030 Profiling
  5. Profiling is used to get runtime information about code code.
  6. There exist several independent profiling tools.
  7. Examples for free profilers are "gprof" for the GNU toolchain and "very sleepy"
  8. from codersnotes for Windows. Proprietary tools are for example "VTune" from
  9. Intel or "AMD APP Profiler". Those tools work by sampling the running
  10. application regularly to get statistic information about the called functions.
  11. The way to use them is to compile your application with special flags
  12. to include profiling information (some also work with debug information). They
  13. also might allow to profile only certain parts of the code, although
  14. most can't do that. The sampling is usually rather time-consuming which means
  15. the application will be very slow when collecting the profiling data. It's often
  16. useful to start with one of those tools to get an overview over the bottlenecks
  17. in your application. Those tools have the advantage that they don't need any
  18. modifications inside the code.
  19. Once you need to dig deeper the Irrlicht profiler can help you. It works nearly
  20. like a stopwatch. You add start/stop blocks into the parts of your code which
  21. you need to check and the Irrlicht profiler will give you then the exact times
  22. of execution for those parts. And unlike general profiler tools you don't just
  23. get average information about the run-time but also worst-cases. Which tends
  24. to be information you really for a stable framerate. Also the Irrlicht profiler
  25. has a low overhead and affects only the areas which you want to time. So you
  26. can profile applications with nearly original speed.
  27. Irrlicht itself has such profiling information, which is useful to figure out
  28. where the runtime inside the engine is spend. To get that profiling data you
  29. need to recompile Irrlicht with _IRR_COMPILE_WITH_PROFILING_ enabled as
  30. collecting profiling information is disabled by default for speed reasons.
  31. */
  32. /*
  33. It's usually a good idea to wrap all your profile code with a define.
  34. That way you don't have to worry too much about the runtime profiling
  35. itself takes. You can remove the profiling code completely when you release
  36. the software by removing a single define.Or sometimes you might want to
  37. have several such defines for different areas of your application code.
  38. */
  39. #define ENABLE_MY_PROFILE // comment out to remove the profiling code
  40. #ifdef ENABLE_MY_PROFILE
  41. // calls code X
  42. #define MY_PROFILE(X) X
  43. #else
  44. // removes the code for X in the pre-processor
  45. #define MY_PROFILE(X)
  46. #endif // IRR_PROFILE
  47. #include <irrlicht.h>
  48. #include "driverChoice.h"
  49. #include "exampleHelper.h"
  50. #ifdef _MSC_VER
  51. #pragma comment(lib, "Irrlicht.lib")
  52. #endif
  53. using namespace irr;
  54. using namespace core;
  55. using namespace scene;
  56. using namespace video;
  57. using namespace io;
  58. using namespace gui;
  59. /*
  60. We have the choice between working with fixed and with automatic profiling id's.
  61. Here are some fixed ID's we will be using.
  62. */
  63. enum EProfiles
  64. {
  65. EP_APP_TIME_ONCE,
  66. EP_APP_TIME_UPDATED,
  67. EP_SCOPE1,
  68. EP_SCOPE2,
  69. EP_DRAW_SCENE
  70. };
  71. // For our example scenes
  72. enum EScenes
  73. {
  74. ES_NONE, // no scene set
  75. ES_CUBE,
  76. ES_QUAKE_MAP,
  77. ES_DWARVES,
  78. ES_COUNT // counting how many scenes we have
  79. };
  80. /*
  81. Controlling the profiling display is application specific behavior.
  82. We use function keys in our case and play around with all the parameters.
  83. In real applications you will likely only need something to make the
  84. profiling-display visible/invisible and switch pages while the parameters
  85. can be set to fixed values.
  86. */
  87. class MyEventReceiver : public IEventReceiver
  88. {
  89. public:
  90. // constructor
  91. MyEventReceiver(ISceneManager * smgr) : GuiProfiler(0), IncludeOverview(true), IgnoreUncalled(false), ActiveScene(ES_NONE), SceneManager(smgr) {}
  92. virtual bool OnEvent(const SEvent& event)
  93. {
  94. if (event.EventType == EET_KEY_INPUT_EVENT)
  95. {
  96. if ( event.KeyInput.PressedDown )
  97. {
  98. /*
  99. Catching keys to control the profiling display and the profiler itself
  100. */
  101. switch ( event.KeyInput.Key )
  102. {
  103. case KEY_F1:
  104. GuiProfiler->setVisible( !GuiProfiler->isVisible() );
  105. break;
  106. case KEY_F2:
  107. GuiProfiler->nextPage(IncludeOverview);
  108. break;
  109. case KEY_F3:
  110. GuiProfiler->previousPage(IncludeOverview);
  111. break;
  112. case KEY_F4:
  113. GuiProfiler->firstPage(IncludeOverview);
  114. break;
  115. case KEY_F5:
  116. IncludeOverview = !IncludeOverview;
  117. GuiProfiler->firstPage(IncludeOverview); // not strictly needed, but otherwise the update won't update
  118. break;
  119. case KEY_F6:
  120. /*
  121. You can set more filters. This one filters out profile data which was never called.
  122. */
  123. IgnoreUncalled = !IgnoreUncalled;
  124. GuiProfiler->setFilters(IgnoreUncalled ? 1 : 0, 0, 0.f, 0);
  125. break;
  126. case KEY_F7:
  127. GuiProfiler->setShowGroupsTogether( !GuiProfiler->getShowGroupsTogether() );
  128. break;
  129. case KEY_F8:
  130. NextScene();
  131. break;
  132. case KEY_F9:
  133. {
  134. u32 index = 0;
  135. if ( getProfiler().findGroupIndex(index, L"grp runtime") )
  136. {
  137. getProfiler().resetGroup(index);
  138. }
  139. }
  140. break;
  141. case KEY_F10:
  142. {
  143. u32 index = 0;
  144. if ( getProfiler().findDataIndex(index, L"scope 3") )
  145. {
  146. getProfiler().resetDataByIndex(index);
  147. }
  148. }
  149. break;
  150. case KEY_F11:
  151. getProfiler().resetAll();
  152. break;
  153. case KEY_KEY_F:
  154. GuiProfiler->setFrozen(!GuiProfiler->getFrozen());
  155. break;
  156. default:
  157. break;
  158. }
  159. }
  160. }
  161. return false;
  162. }
  163. /*
  164. Some example scenes so we have something to profile
  165. */
  166. void NextScene()
  167. {
  168. SceneManager->clear();
  169. ActiveScene = (ActiveScene+1) % ES_COUNT;
  170. if ( ActiveScene == 0 )
  171. ActiveScene = ActiveScene+1;
  172. switch ( ActiveScene )
  173. {
  174. case ES_CUBE:
  175. {
  176. /*
  177. Simple scene with cube and light.
  178. */
  179. MY_PROFILE(CProfileScope p(L"cube", L"grp switch scene");)
  180. SceneManager->addCameraSceneNode (0, core::vector3df(0, 0, 0),
  181. core::vector3df(0, 0, 100),
  182. -1);
  183. SceneManager->addCubeSceneNode (30.0f, 0, -1,
  184. core::vector3df(0, 20, 100),
  185. core::vector3df(45, 45, 45),
  186. core::vector3df(1.0f, 1.0f, 1.0f));
  187. SceneManager->addLightSceneNode(0, core::vector3df(0, 0, 0),
  188. video::SColorf(1.0f, 1.0f, 1.0f),
  189. 100.0f);
  190. }
  191. break;
  192. case ES_QUAKE_MAP:
  193. {
  194. /*
  195. Our typical Irrlicht example quake map.
  196. */
  197. MY_PROFILE(CProfileScope p(L"quake map", L"grp switch scene");)
  198. scene::IAnimatedMesh* mesh = SceneManager->getMesh("20kdm2.bsp");
  199. scene::ISceneNode* node = 0;
  200. if (mesh)
  201. node = SceneManager->addOctreeSceneNode(mesh->getMesh(0), 0, -1, 1024);
  202. if (node)
  203. node->setPosition(core::vector3df(-1300,-144,-1249));
  204. SceneManager->addCameraSceneNodeFPS();
  205. }
  206. break;
  207. case ES_DWARVES:
  208. {
  209. /*
  210. Stress-test Irrlicht a little bit by creating many objects.
  211. */
  212. MY_PROFILE(CProfileScope p(L"dwarfes", L"grp switch scene");)
  213. scene::IAnimatedMesh* aniMesh = SceneManager->getMesh( getExampleMediaPath() + "dwarf.x" );
  214. if (aniMesh)
  215. {
  216. scene::IMesh * mesh = aniMesh->getMesh (0);
  217. if ( !mesh )
  218. break;
  219. /*
  220. You can never have too many dwarves. So let's make some.
  221. */
  222. const int nodesX = 30;
  223. const int nodesY = 5;
  224. const int nodesZ = 30;
  225. aabbox3df bbox = mesh->getBoundingBox();
  226. vector3df extent = bbox.getExtent();
  227. const f32 GAP = 10.f;
  228. f32 halfSizeX = 0.5f * (nodesX*extent.X + GAP*(nodesX-1));
  229. f32 halfSizeY = 0.5f * (nodesY*extent.Y + GAP*(nodesY-1));
  230. f32 halfSizeZ = 0.5f * (nodesZ*extent.Z + GAP*(nodesZ-1));
  231. for ( int x = 0; x < nodesX; ++x )
  232. {
  233. irr::f32 gapX = x > 0 ? (x-1)*GAP : 0.f;
  234. irr::f32 posX = -halfSizeX + x*extent.X + gapX;
  235. for ( int y = 0; y < nodesY; ++y )
  236. {
  237. irr::f32 gapY = y > 0 ? (y-1)*GAP : 0.f;
  238. irr::f32 posY = -halfSizeY + y*extent.Y + gapY;
  239. for ( int z=0; z < nodesZ; ++z )
  240. {
  241. irr::f32 gapZ = z > 0 ? (z-1)*GAP : 0.f;
  242. irr::f32 posZ = -halfSizeZ + z*extent.Z + gapZ;
  243. scene::IAnimatedMeshSceneNode * node = SceneManager->addAnimatedMeshSceneNode(aniMesh, NULL, -1, vector3df(posX, posY, posZ) );
  244. node->setMaterialFlag(video::EMF_LIGHTING, false);
  245. }
  246. }
  247. }
  248. irr::scene::ICameraSceneNode * camera = SceneManager->addCameraSceneNodeFPS(0, 20.f, 0.1f );
  249. camera->updateAbsolutePosition();
  250. camera->setTarget( vector3df(0,0,0) );
  251. camera->updateAbsolutePosition();
  252. camera->setPosition(irr::core::vector3df(halfSizeX+extent.X, halfSizeY+extent.Y, halfSizeZ+extent.Z));
  253. camera->updateAbsolutePosition();
  254. }
  255. }
  256. break;
  257. }
  258. }
  259. IGUIProfiler * GuiProfiler;
  260. bool IncludeOverview;
  261. bool IgnoreUncalled;
  262. u32 ActiveScene;
  263. scene::ISceneManager* SceneManager;
  264. };
  265. void recursive(int recursion)
  266. {
  267. /*
  268. As the profiler uses internally counters for start stop and only
  269. takes profile data when that counter is zero we count all recursions
  270. as a single call.
  271. If you want to profile each call on it's own you have to use explicit start/stop calls and
  272. stop the profile id right before the recursive call.
  273. */
  274. MY_PROFILE(CProfileScope p3(L"recursive", L"grp runtime");)
  275. if (recursion > 0 )
  276. recursive(recursion-1);
  277. }
  278. int main()
  279. {
  280. /*
  281. Setup, nothing special here.
  282. */
  283. video::E_DRIVER_TYPE driverType=driverChoiceConsole();
  284. if (driverType==video::EDT_COUNT)
  285. return 1;
  286. /*
  287. Profiler is independent of the device - so we can time the device setup
  288. */
  289. MY_PROFILE(s32 pDev = getProfiler().add(L"createDevice", L"grp runtime");)
  290. MY_PROFILE(getProfiler().start(pDev);)
  291. IrrlichtDevice * device = createDevice(driverType, core::dimension2d<u32>(640, 480));
  292. if (device == 0)
  293. {
  294. /*
  295. When working with start/stop you should add a stop to all exit paths.
  296. Although in this case it wouldn't matter as we don't do anything with it when we quit here.
  297. */
  298. MY_PROFILE(getProfiler().stop(pDev);)
  299. return 1; // could not create selected driver.
  300. }
  301. MY_PROFILE(getProfiler().stop(pDev);)
  302. video::IVideoDriver* driver = device->getVideoDriver();
  303. IGUIEnvironment* env = device->getGUIEnvironment();
  304. scene::ISceneManager* smgr = device->getSceneManager();
  305. const io::path mediaPath = getExampleMediaPath();
  306. /*
  307. A map we use for one of our test-scenes.
  308. */
  309. device->getFileSystem()->addFileArchive(mediaPath + "map-20kdm2.pk3");
  310. MyEventReceiver receiver(smgr);
  311. device->setEventReceiver(&receiver);
  312. receiver.NextScene();
  313. /*
  314. Show some info about the controls used in this example
  315. */
  316. IGUIStaticText * staticText = env->addStaticText(
  317. L"<F1> to show/hide the profiling display\n"
  318. L"<F2> to show the next page\n"
  319. L"<F3> to show the previous page\n"
  320. L"<F4> to show the first page\n"
  321. L"<F5> to flip between including the group overview\n"
  322. L"<F6> to flip between ignoring and showing uncalled data\n"
  323. L"<F7> to flip between showing 1 group per page or all together\n"
  324. L"<F8> to change our scene\n"
  325. L"<F9> to reset the \"grp runtime\" data\n"
  326. L"<F10> to reset the scope 3 data\n"
  327. L"<F11> to reset all data\n"
  328. L"<f> to freeze/unfreeze the display\n"
  329. , recti(10,10, 250, 140), true, true, 0, -1, true);
  330. staticText->setWordWrap(false);
  331. /*
  332. IGUIProfiler is can be used to show active profiling data at runtime.
  333. */
  334. receiver.GuiProfiler = env->addProfilerDisplay(core::recti(40, 140, 600, 470));
  335. receiver.GuiProfiler->setDrawBackground(true);
  336. /*
  337. Get a monospaced font - it's nicer when working with rows of numbers.
  338. */
  339. IGUIFont* font = env->getFont(mediaPath + "fontcourier.bmp");
  340. if (font)
  341. receiver.GuiProfiler->setOverrideFont(font);
  342. /*
  343. Adding ID's has to be done before the start/stop calls.
  344. This allows start/stop to be really fast and we still have nice information like
  345. names and groups.
  346. Groups are created automatically each time an ID with a new group-name is added.
  347. Groups exist to sort the display data in a nicer way.
  348. */
  349. MY_PROFILE(
  350. getProfiler().add(EP_APP_TIME_ONCE, L"full time", L"grp runtime");
  351. getProfiler().add(EP_APP_TIME_UPDATED, L"full time updated", L"grp runtime");
  352. getProfiler().add(EP_SCOPE1, L"scope 1", L"grp runtime");
  353. getProfiler().add(EP_DRAW_SCENE, L"draw scene", L"grp runtime");
  354. )
  355. /*
  356. Two timers which run the whole time. One will be continuously updated the other won't.
  357. */
  358. MY_PROFILE(getProfiler().start(EP_APP_TIME_ONCE);)
  359. MY_PROFILE(getProfiler().start(EP_APP_TIME_UPDATED);)
  360. s32 lastFPS = -1;
  361. while(device->run() && driver)
  362. {
  363. if (device->isWindowActive())
  364. {
  365. /*
  366. For comparison show the FPS in the title bar
  367. */
  368. s32 fps = driver->getFPS();
  369. if (lastFPS != fps)
  370. {
  371. core::stringw str = L"FPS: ";
  372. str += fps;
  373. device->setWindowCaption(str.c_str());
  374. lastFPS = fps;
  375. }
  376. /*
  377. Times are only updated on stop() calls. So if we want a long-running timer
  378. to update we have to stop() and start() it in between.
  379. Note that this will also update the call-counter and is rarely needed.
  380. */
  381. MY_PROFILE(getProfiler().stop(EP_APP_TIME_UPDATED);)
  382. MY_PROFILE(getProfiler().start(EP_APP_TIME_UPDATED);)
  383. /*
  384. The following CProfileScope's will all do the same thing:
  385. they measure the time this loop takes. They call start()
  386. when the object is created and call stop() when it
  387. is destroyed.
  388. The first one creates an ID on it's first call and will
  389. do constant string-comparisons for the name. It's
  390. the slowest, but most comfortable solution. Use it when you
  391. just need to run a quick check without the hassle of setting
  392. up id's.
  393. */
  394. MY_PROFILE(CProfileScope p3(L"scope 3", L"grp runtime");)
  395. /*
  396. Second CProfileScope solution will create a data block on first
  397. call. So it's a little bit slower on the first run. But usually
  398. that's hardly noticeable.
  399. */
  400. MY_PROFILE(CProfileScope p2(EP_SCOPE2, L"scope 2", L"grp runtime");)
  401. /*
  402. Last CProfileScope solution is the fastest one. But you must add
  403. the id before you can use it like that.
  404. */
  405. MY_PROFILE(CProfileScope p1(EP_SCOPE1));
  406. /*
  407. Call a recursive function to show how profiler only counts it once.
  408. */
  409. recursive(5);
  410. driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));
  411. /*
  412. If you want to profile only some lines and not a complete scope
  413. then you have to work with start() and stop() calls.
  414. */
  415. MY_PROFILE(getProfiler().start(EP_DRAW_SCENE);)
  416. smgr->drawAll();
  417. MY_PROFILE(getProfiler().stop(EP_DRAW_SCENE);)
  418. /*
  419. If it doesn't matter if the profiler takes some time you can also
  420. be lazy and create id's automatically on the spot:
  421. */
  422. MY_PROFILE(s32 pEnv = getProfiler().add(L"draw env", L"grp runtime");)
  423. MY_PROFILE(getProfiler().start(pEnv);)
  424. env->drawAll();
  425. MY_PROFILE(getProfiler().stop(pEnv);)
  426. driver->endScene();
  427. }
  428. }
  429. /*
  430. Shutdown.
  431. */
  432. device->drop();
  433. /*
  434. The profiler is independent of an device - so we can still work with it.
  435. */
  436. MY_PROFILE(getProfiler().stop(EP_APP_TIME_UPDATED));
  437. MY_PROFILE(getProfiler().stop(EP_APP_TIME_ONCE));
  438. /*
  439. Print a complete overview of the profiling data to the console.
  440. */
  441. MY_PROFILE(core::stringw output);
  442. MY_PROFILE(getProfiler().printAll(output));
  443. MY_PROFILE(printf("%s", core::stringc(output).c_str() ));
  444. return 0;
  445. }
  446. /*
  447. **/