StatsTab.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. import { Engine, EngineInstrumentation, Nullable, Scene, SceneInstrumentation, Tools } from "babylonjs";
  2. import { Helpers } from "../helpers/Helpers";
  3. import { Inspector } from "../Inspector";
  4. import { Tab } from "./Tab";
  5. import { TabBar } from "./TabBar";
  6. export class StatsTab extends Tab {
  7. private _inspector: Inspector;
  8. /**
  9. * Properties in this array will be updated
  10. * in a render loop - Mostly stats properties
  11. */
  12. private _updatableProperties: Array<{ elem: HTMLElement, updateFct: () => string }> = [];
  13. private _scene: Scene;
  14. private _engine: Engine;
  15. private _glInfo: any;
  16. private _updateLoopHandler: any;
  17. private _refreshRateCounter: any;
  18. private refreshRate: any;
  19. private _sceneInstrumentation: Nullable<SceneInstrumentation>;
  20. private _engineInstrumentation: Nullable<EngineInstrumentation>;
  21. private _inputElement: HTMLInputElement;
  22. private _connectToInstrumentation() {
  23. if (this._sceneInstrumentation) {
  24. return;
  25. }
  26. this._sceneInstrumentation = new SceneInstrumentation(this._scene);
  27. this._sceneInstrumentation.captureActiveMeshesEvaluationTime = true;
  28. this._sceneInstrumentation.captureRenderTargetsRenderTime = true;
  29. this._sceneInstrumentation.captureFrameTime = true;
  30. this._sceneInstrumentation.captureRenderTime = true;
  31. this._sceneInstrumentation.captureInterFrameTime = true;
  32. this._sceneInstrumentation.captureParticlesRenderTime = true;
  33. this._sceneInstrumentation.captureSpritesRenderTime = true;
  34. this._sceneInstrumentation.capturePhysicsTime = true;
  35. this._sceneInstrumentation.captureAnimationsTime = true;
  36. this._engineInstrumentation = new EngineInstrumentation(this._engine);
  37. this._engineInstrumentation.captureGPUFrameTime = true;
  38. }
  39. constructor(tabbar: TabBar, insp: Inspector) {
  40. super(tabbar, 'Stats');
  41. this._inspector = insp;
  42. this._scene = this._inspector.scene;
  43. this._engine = this._scene.getEngine();
  44. this._glInfo = this._engine.getGlInfo();
  45. this._connectToInstrumentation();
  46. // Build the stats panel: a div that will contains all stats
  47. this._panel = Helpers.CreateDiv('tab-panel') as HTMLDivElement;
  48. this._panel.classList.add("stats-panel")
  49. let title = Helpers.CreateDiv('stat-title1', this._panel);
  50. let fpsSpan = Helpers.CreateElement('span', 'stats-fps');
  51. this._updatableProperties.push({
  52. elem: fpsSpan,
  53. updateFct: () => { return Tools.Format(this._inspector.scene.getEngine().getFps(), 0) + " fps" }
  54. });
  55. let versionSpan = Helpers.CreateElement('span');
  56. versionSpan.textContent = `js v${Engine.Version} - `;
  57. title.appendChild(versionSpan);
  58. title.appendChild(fpsSpan);
  59. this._updateLoopHandler = this._update.bind(this);
  60. this._refreshRateCounter = 0;
  61. this.refreshRate = 4;
  62. // Count block
  63. title = Helpers.CreateDiv('stat-title2', this._panel);
  64. title.textContent = "Count";
  65. {
  66. this._createStatLabel("Total meshes", this._panel);
  67. let elemValue = Helpers.CreateDiv('stat-value', this._panel);
  68. this._updatableProperties.push({
  69. elem: elemValue,
  70. updateFct: () => { return this._scene.meshes.length.toString() }
  71. });
  72. this._createStatLabel("Draw calls", this._panel);
  73. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  74. this._updatableProperties.push({
  75. elem: elemValue,
  76. updateFct: () => { return this._sceneInstrumentation!.drawCallsCounter.current.toString() }
  77. });
  78. this._createStatLabel("Texture collisions", this._panel);
  79. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  80. this._updatableProperties.push({
  81. elem: elemValue,
  82. updateFct: () => { return this._sceneInstrumentation!.textureCollisionsCounter.current.toString() }
  83. });
  84. this._createStatLabel("Total lights", this._panel);
  85. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  86. this._updatableProperties.push({
  87. elem: elemValue,
  88. updateFct: () => { return this._scene.lights.length.toString() }
  89. });
  90. this._createStatLabel("Total vertices", this._panel);
  91. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  92. this._updatableProperties.push({
  93. elem: elemValue,
  94. updateFct: () => { return this._scene.getTotalVertices().toString() }
  95. });
  96. this._createStatLabel("Total materials", this._panel);
  97. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  98. this._updatableProperties.push({
  99. elem: elemValue,
  100. updateFct: () => { return this._scene.materials.length.toString() }
  101. });
  102. this._createStatLabel("Total textures", this._panel);
  103. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  104. this._updatableProperties.push({
  105. elem: elemValue,
  106. updateFct: () => { return this._scene.textures.length.toString() }
  107. });
  108. this._createStatLabel("Active meshes", this._panel);
  109. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  110. this._updatableProperties.push({
  111. elem: elemValue,
  112. updateFct: () => { return this._scene.getActiveMeshes().length.toString() }
  113. });
  114. this._createStatLabel("Active indices", this._panel);
  115. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  116. this._updatableProperties.push({
  117. elem: elemValue,
  118. updateFct: () => { return this._scene.getActiveIndices().toString() }
  119. });
  120. this._createStatLabel("Active bones", this._panel);
  121. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  122. this._updatableProperties.push({
  123. elem: elemValue,
  124. updateFct: () => { return this._scene.getActiveBones().toString() }
  125. });
  126. this._createStatLabel("Active particles", this._panel);
  127. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  128. this._updatableProperties.push({
  129. elem: elemValue,
  130. updateFct: () => { return this._scene.getActiveParticles().toString() }
  131. });
  132. }
  133. title = Helpers.CreateDiv('stat-title2', this._panel);
  134. title.textContent = "Duration";
  135. {
  136. this._createStatLabel("Properties refresh rate (per second)", this._panel);
  137. let elemValue = Helpers.CreateDiv('stat-value', this._panel);
  138. this._inputElement = Inspector.DOCUMENT.createElement('input');
  139. this._inputElement.value = this.refreshRate;
  140. elemValue.appendChild(this._inputElement);
  141. this._inputElement.addEventListener('keyup', (evt: KeyboardEvent) => {
  142. this.refreshRate = this._inputElement.value;
  143. })
  144. this._createStatLabel("Meshes selection", this._panel);
  145. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  146. this._updatableProperties.push({
  147. elem: elemValue,
  148. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.activeMeshesEvaluationTimeCounter.current) }
  149. });
  150. this._createStatLabel("Render targets", this._panel);
  151. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  152. this._updatableProperties.push({
  153. elem: elemValue,
  154. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.renderTargetsRenderTimeCounter.current) }
  155. });
  156. this._createStatLabel("Particles", this._panel);
  157. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  158. this._updatableProperties.push({
  159. elem: elemValue,
  160. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.particlesRenderTimeCounter.current) }
  161. });
  162. this._createStatLabel("Sprites", this._panel);
  163. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  164. this._updatableProperties.push({
  165. elem: elemValue,
  166. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.spritesRenderTimeCounter.current) }
  167. });
  168. this._createStatLabel("Animations", this._panel);
  169. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  170. this._updatableProperties.push({
  171. elem: elemValue,
  172. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.animationsTimeCounter.current) }
  173. });
  174. this._createStatLabel("Physics", this._panel);
  175. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  176. this._updatableProperties.push({
  177. elem: elemValue,
  178. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.physicsTimeCounter.current) }
  179. });
  180. this._createStatLabel("Render", this._panel);
  181. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  182. this._updatableProperties.push({
  183. elem: elemValue,
  184. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.renderTimeCounter.current) }
  185. });
  186. this._createStatLabel("Frame", this._panel);
  187. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  188. this._updatableProperties.push({
  189. elem: elemValue,
  190. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.frameTimeCounter.current) }
  191. });
  192. this._createStatLabel("Inter-frame", this._panel);
  193. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  194. this._updatableProperties.push({
  195. elem: elemValue,
  196. updateFct: () => { return Tools.Format(this._sceneInstrumentation!.interFrameTimeCounter.current) }
  197. });
  198. this._createStatLabel("GPU Frame time", this._panel);
  199. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  200. this._updatableProperties.push({
  201. elem: elemValue,
  202. updateFct: () => { return Tools.Format(this._engineInstrumentation!.gpuFrameTimeCounter.current * 0.000001) }
  203. });
  204. this._createStatLabel("GPU Frame time (average)", this._panel);
  205. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  206. this._updatableProperties.push({
  207. elem: elemValue,
  208. updateFct: () => { return Tools.Format(this._engineInstrumentation!.gpuFrameTimeCounter.average * 0.000001) }
  209. });
  210. this._createStatLabel("Potential FPS", this._panel);
  211. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  212. this._updatableProperties.push({
  213. elem: elemValue,
  214. updateFct: () => { return Tools.Format(1000.0 / this._sceneInstrumentation!.frameTimeCounter.current, 0) }
  215. });
  216. this._createStatLabel("Resolution", this._panel);
  217. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  218. this._updatableProperties.push({
  219. elem: elemValue,
  220. updateFct: () => { return this._engine.getRenderWidth() + "x" + this._engine.getRenderHeight() }
  221. });
  222. }
  223. title = Helpers.CreateDiv('stat-title2', this._panel);
  224. title.textContent = "Extensions";
  225. {
  226. this._createStatLabel("Std derivatives", this._panel);
  227. let elemValue = Helpers.CreateDiv('stat-value', this._panel);
  228. this._updatableProperties.push({
  229. elem: elemValue,
  230. updateFct: () => { return (this._engine.getCaps().standardDerivatives ? "Yes" : "No") }
  231. });
  232. this._createStatLabel("Compressed textures", this._panel);
  233. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  234. this._updatableProperties.push({
  235. elem: elemValue,
  236. updateFct: () => { return (this._engine.getCaps().s3tc ? "Yes" : "No") }
  237. });
  238. this._createStatLabel("Hardware instances", this._panel);
  239. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  240. this._updatableProperties.push({
  241. elem: elemValue,
  242. updateFct: () => { return (this._engine.getCaps().instancedArrays ? "Yes" : "No") }
  243. });
  244. this._createStatLabel("Texture float", this._panel);
  245. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  246. this._updatableProperties.push({
  247. elem: elemValue,
  248. updateFct: () => { return (this._engine.getCaps().textureFloat ? "Yes" : "No") }
  249. });
  250. this._createStatLabel("32bits indices", this._panel);
  251. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  252. this._updatableProperties.push({
  253. elem: elemValue,
  254. updateFct: () => { return (this._engine.getCaps().uintIndices ? "Yes" : "No") }
  255. });
  256. this._createStatLabel("Fragment depth", this._panel);
  257. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  258. this._updatableProperties.push({
  259. elem: elemValue,
  260. updateFct: () => { return (this._engine.getCaps().fragmentDepthSupported ? "Yes" : "No") }
  261. });
  262. this._createStatLabel("High precision shaders", this._panel);
  263. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  264. this._updatableProperties.push({
  265. elem: elemValue,
  266. updateFct: () => { return (this._engine.getCaps().highPrecisionShaderSupported ? "Yes" : "No") }
  267. });
  268. this._createStatLabel("Draw buffers", this._panel);
  269. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  270. this._updatableProperties.push({
  271. elem: elemValue,
  272. updateFct: () => { return (this._engine.getCaps().drawBuffersExtension ? "Yes" : "No") }
  273. });
  274. this._createStatLabel("Vertex array object", this._panel);
  275. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  276. this._updatableProperties.push({
  277. elem: elemValue,
  278. updateFct: () => { return (this._engine.getCaps().vertexArrayObject ? "Yes" : "No") }
  279. });
  280. this._createStatLabel("Timer query", this._panel);
  281. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  282. this._updatableProperties.push({
  283. elem: elemValue,
  284. updateFct: () => { return (this._engine.getCaps().timerQuery ? "Yes" : "No") }
  285. });
  286. }
  287. title = Helpers.CreateDiv('stat-title2', this._panel);
  288. title.textContent = "Caps.";
  289. {
  290. this._createStatLabel("Stencil", this._panel);
  291. let elemValue = Helpers.CreateDiv('stat-value', this._panel);
  292. this._updatableProperties.push({
  293. elem: elemValue,
  294. updateFct: () => { return (this._engine.isStencilEnable ? "Enabled" : "Disabled") }
  295. });
  296. this._createStatLabel("Max textures units", this._panel);
  297. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  298. this._updatableProperties.push({
  299. elem: elemValue,
  300. updateFct: () => { return this._engine.getCaps().maxTexturesImageUnits.toString() }
  301. });
  302. this._createStatLabel("Max textures size", this._panel);
  303. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  304. this._updatableProperties.push({
  305. elem: elemValue,
  306. updateFct: () => { return this._engine.getCaps().maxTextureSize.toString() }
  307. });
  308. this._createStatLabel("Max anisotropy", this._panel);
  309. elemValue = Helpers.CreateDiv('stat-value', this._panel);
  310. this._updatableProperties.push({
  311. elem: elemValue,
  312. updateFct: () => { return this._engine.getCaps().maxAnisotropy.toString() }
  313. });
  314. }
  315. title = Helpers.CreateDiv('stat-title2', this._panel);
  316. title.textContent = "Info";
  317. {
  318. let elemValue = Helpers.CreateDiv('stat-infos', this._panel);
  319. this._updatableProperties.push({
  320. elem: elemValue,
  321. updateFct: () => { return "WebGL v" + this._engine.webGLVersion + " - " + this._glInfo.version + " - " + this._glInfo.renderer }
  322. });
  323. }
  324. }
  325. private _createStatLabel(content: string, parent: HTMLElement): HTMLElement {
  326. let elem = Helpers.CreateDiv('stat-label', parent);
  327. elem.textContent = content;
  328. return elem;
  329. }
  330. /** Update each properties of the stats panel */
  331. private _update() {
  332. if (this._refreshRateCounter > 1) {
  333. this._refreshRateCounter--;
  334. } else {
  335. for (let prop of this._updatableProperties) {
  336. prop.elem.textContent = prop.updateFct();
  337. }
  338. if (this._inspector.scene.getEngine().getFps() / this.refreshRate == Infinity) {
  339. this._refreshRateCounter = 1;
  340. } else {
  341. this._refreshRateCounter = this._inspector.scene.getEngine().getFps() / this.refreshRate;
  342. }
  343. }
  344. }
  345. public dispose() {
  346. this._scene.unregisterAfterRender(this._updateLoopHandler);
  347. this._sceneInstrumentation!.dispose();
  348. this._sceneInstrumentation = null;
  349. this._engineInstrumentation!.dispose();
  350. this._engineInstrumentation = null;
  351. }
  352. public active(b: boolean) {
  353. super.active(b);
  354. if (b) {
  355. this._connectToInstrumentation();
  356. this._scene.registerAfterRender(this._updateLoopHandler);
  357. }
  358. }
  359. }