Inspector.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import { RendererInspector } from './RendererInspector.js';
  2. import { Profiler } from './ui/Profiler.js';
  3. import { Performance } from './tabs/Performance.js';
  4. import { Memory } from './tabs/Memory.js';
  5. import { Console } from './tabs/Console.js';
  6. import { Parameters } from './tabs/Parameters.js';
  7. import { Settings } from './tabs/Settings.js';
  8. import { Viewer } from './tabs/Viewer.js';
  9. import { Timeline } from './tabs/Timeline.js';
  10. import { setText } from './ui/utils.js';
  11. import { setConsoleFunction, REVISION } from 'three/webgpu';
  12. class Inspector extends RendererInspector {
  13. constructor() {
  14. super();
  15. // init profiler
  16. const profiler = new Profiler( this );
  17. profiler.addEventListener( 'resize', ( e ) => this.dispatchEvent( e ) );
  18. const parameters = new Parameters( {
  19. builtin: true,
  20. icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 6l8 0" /><path d="M16 6l4 0" /><path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 12l2 0" /><path d="M10 12l10 0" /><path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 18l11 0" /><path d="M19 18l1 0" /></svg>'
  21. } );
  22. parameters.hide();
  23. profiler.addTab( parameters );
  24. const viewer = new Viewer();
  25. viewer.hide();
  26. profiler.addTab( viewer );
  27. const performance = new Performance();
  28. profiler.addTab( performance );
  29. const memory = new Memory();
  30. profiler.addTab( memory );
  31. const timeline = new Timeline();
  32. profiler.addTab( timeline );
  33. const consoleTab = new Console();
  34. profiler.addTab( consoleTab );
  35. const settings = new Settings();
  36. profiler.addTab( settings );
  37. profiler.loadLayout();
  38. if ( ! profiler.activeTabId ) {
  39. profiler.setActiveTab( performance.id );
  40. }
  41. this.statsData = new Map();
  42. this.profiler = profiler;
  43. this.performance = performance;
  44. this.memory = memory;
  45. this.console = consoleTab;
  46. this.parameters = parameters;
  47. this.viewer = viewer;
  48. this.timeline = timeline;
  49. this.settings = settings;
  50. this.once = {};
  51. this.extensionsData = new WeakMap();
  52. this.displayCycle = {
  53. text: {
  54. needsUpdate: false,
  55. duration: .25,
  56. time: 0
  57. },
  58. graph: {
  59. needsUpdate: false,
  60. duration: .02,
  61. time: 0
  62. },
  63. toggleGraph: {
  64. needsUpdate: false,
  65. duration: .02,
  66. time: 0
  67. }
  68. };
  69. }
  70. get domElement() {
  71. return this.profiler.domElement;
  72. }
  73. onExtension( name, callback ) {
  74. const extensionAdded = ( e ) => {
  75. if ( e.name === name ) {
  76. callback( e.tab );
  77. this.settings.removeEventListener( 'extensionadded', extensionAdded );
  78. }
  79. };
  80. if ( this.settings.extensions[ name ] && this.settings.extensions[ name ].loaded ) {
  81. callback( this.settings.extensions[ name ] );
  82. } else {
  83. this.settings.addEventListener( 'extensionadded', extensionAdded );
  84. }
  85. return this;
  86. }
  87. hide() {
  88. this.profiler.hide();
  89. }
  90. show() {
  91. this.profiler.show();
  92. }
  93. getSize() {
  94. return this.profiler.getSize();
  95. }
  96. setActiveTab( tab ) {
  97. this.profiler.setActiveTab( tab.id );
  98. return this;
  99. }
  100. addTab( tab ) {
  101. this.profiler.addTab( tab );
  102. return this;
  103. }
  104. removeTab( tab ) {
  105. this.profiler.removeTab( tab );
  106. return this;
  107. }
  108. setActiveExtension( name, value ) {
  109. this.settings.setActiveExtension( name, value );
  110. return this;
  111. }
  112. resolveConsoleOnce( type, message ) {
  113. const key = type + message;
  114. if ( this.once[ key ] !== true ) {
  115. this.resolveConsole( type, message );
  116. this.once[ key ] = true;
  117. }
  118. }
  119. resolveConsole( type, message, stackTrace = null ) {
  120. switch ( type ) {
  121. case 'log':
  122. this.console.addMessage( 'info', message );
  123. console.log( message );
  124. break;
  125. case 'warn':
  126. this.console.addMessage( 'warn', message );
  127. if ( stackTrace && stackTrace.isStackTrace ) {
  128. console.warn( stackTrace.getError( message ) );
  129. } else {
  130. console.warn( message );
  131. }
  132. break;
  133. case 'error':
  134. this.console.addMessage( 'error', message );
  135. if ( stackTrace && stackTrace.isStackTrace ) {
  136. console.error( stackTrace.getError( message ) );
  137. } else {
  138. console.error( message );
  139. }
  140. break;
  141. }
  142. }
  143. init() {
  144. const renderer = this.getRenderer();
  145. let sign = `THREE.WebGPURenderer: ${ REVISION } [ "`;
  146. if ( renderer.backend.isWebGPUBackend ) {
  147. sign += 'WebGPU';
  148. } else if ( renderer.backend.isWebGLBackend ) {
  149. sign += 'WebGL2';
  150. }
  151. sign += '" ]';
  152. this.console.addMessage( 'info', sign );
  153. //
  154. if ( renderer.inspector.domElement.parentElement === null ) {
  155. if ( renderer.domElement.parentElement !== null ) {
  156. renderer.domElement.parentElement.appendChild( renderer.inspector.domElement );
  157. } else {
  158. const observer = new MutationObserver( () => {
  159. if ( renderer.domElement.parentElement !== null ) {
  160. renderer.domElement.parentElement.appendChild( renderer.inspector.domElement );
  161. observer.disconnect();
  162. }
  163. } );
  164. observer.observe( document.body || document.documentElement, { childList: true, subtree: true } );
  165. }
  166. }
  167. }
  168. setRenderer( renderer ) {
  169. super.setRenderer( renderer );
  170. if ( renderer !== null ) {
  171. setConsoleFunction( this.resolveConsole.bind( this ) );
  172. if ( this.isAvailable ) {
  173. renderer.init().then( () => {
  174. renderer.backend.trackTimestamp = true;
  175. if ( renderer.hasFeature( 'timestamp-query' ) !== true ) {
  176. this.console.addMessage( 'error', 'THREE.Inspector: GPU Timestamp Queries not available.' );
  177. }
  178. } );
  179. this.timeline.setRenderer( renderer );
  180. }
  181. }
  182. return this;
  183. }
  184. createParameters( name ) {
  185. if ( this.parameters.isVisible === false ) {
  186. this.parameters.show();
  187. }
  188. return this.parameters.createGroup( name );
  189. }
  190. getStatsData( cid ) {
  191. let data = this.statsData.get( cid );
  192. if ( data === undefined ) {
  193. data = {};
  194. this.statsData.set( cid, data );
  195. }
  196. return data;
  197. }
  198. resolveStats( stats ) {
  199. const data = this.getStatsData( stats.cid );
  200. if ( data.initialized !== true ) {
  201. data.cpu = stats.cpu;
  202. data.gpu = stats.gpu;
  203. data.stats = [];
  204. data.initialized = true;
  205. }
  206. // store stats
  207. if ( data.stats.length > this.maxFrames ) {
  208. data.stats.shift();
  209. }
  210. data.stats.push( stats );
  211. // compute averages
  212. data.cpu = this.getAverageDeltaTime( data, 'cpu' );
  213. data.gpu = this.getAverageDeltaTime( data, 'gpu' );
  214. data.total = data.cpu + data.gpu;
  215. // children
  216. for ( const child of stats.children ) {
  217. this.resolveStats( child );
  218. const childData = this.getStatsData( child.cid );
  219. data.cpu += childData.cpu;
  220. data.gpu += childData.gpu;
  221. data.total += childData.total;
  222. }
  223. }
  224. getNodes() {
  225. return this.currentNodes;
  226. }
  227. getAverageDeltaTime( statsData, property, frames = this.fps ) {
  228. const statsArray = statsData.stats;
  229. let sum = 0;
  230. let count = 0;
  231. for ( let i = statsArray.length - 1; i >= 0 && count < frames; i -- ) {
  232. const stats = statsArray[ i ];
  233. const value = stats[ property ];
  234. if ( value > 0 ) {
  235. // ignore invalid values
  236. sum += value;
  237. count ++;
  238. }
  239. }
  240. return count > 0 ? sum / count : 0;
  241. }
  242. updateTabs() {
  243. // tabs
  244. const tabs = Object.values( this.profiler.tabs );
  245. for ( const tab of tabs ) {
  246. let tabData = this.extensionsData.get( tab );
  247. if ( tabData === undefined ) {
  248. tab.init( this );
  249. tabData = {};
  250. this.extensionsData.set( tab, tabData );
  251. }
  252. tab.update( this );
  253. }
  254. }
  255. resolveFrame( frame ) {
  256. const nextFrame = this.getFrameById( frame.frameId + 1 );
  257. if ( ! nextFrame ) return;
  258. frame.cpu = 0;
  259. frame.gpu = 0;
  260. frame.total = 0;
  261. for ( const stats of frame.children ) {
  262. this.resolveStats( stats );
  263. const data = this.getStatsData( stats.cid );
  264. frame.cpu += data.cpu;
  265. frame.gpu += data.gpu;
  266. frame.total += data.total;
  267. }
  268. // improve stats using next frame
  269. frame.deltaTime = nextFrame.startTime - frame.startTime;
  270. frame.miscellaneous = frame.deltaTime - frame.total;
  271. if ( frame.miscellaneous < 0 ) {
  272. // Frame desync, probably due to async GPU timing.
  273. frame.miscellaneous = 0;
  274. }
  275. //
  276. this.updateCycle( this.displayCycle.text );
  277. this.updateCycle( this.displayCycle.graph );
  278. this.updateCycle( this.displayCycle.toggleGraph );
  279. if ( this.displayCycle.text.needsUpdate ) {
  280. setText( this.profiler.toggleButton.querySelector( '.fps-counter' ), this.fps.toFixed() );
  281. this.performance.updateText( this, frame );
  282. this.memory.updateText( this );
  283. }
  284. if ( this.displayCycle.toggleGraph.needsUpdate ) {
  285. if ( this.profiler.toggleGraph ) {
  286. this.profiler.toggleGraph.addPoint( 'fps', this.fps );
  287. this.profiler.toggleGraph.update();
  288. }
  289. }
  290. if ( this.displayCycle.graph.needsUpdate ) {
  291. this.performance.updateGraph( this, frame );
  292. this.memory.updateGraph( this );
  293. }
  294. this.displayCycle.text.needsUpdate = false;
  295. this.displayCycle.graph.needsUpdate = false;
  296. this.displayCycle.toggleGraph.needsUpdate = false;
  297. }
  298. updateCycle( cycle ) {
  299. cycle.time += this.nodeFrame.deltaTime;
  300. if ( cycle.time >= cycle.duration ) {
  301. cycle.needsUpdate = true;
  302. cycle.time = 0;
  303. }
  304. }
  305. }
  306. function getItem( id ) {
  307. const data = JSON.parse( localStorage.getItem( 'threejs-inspector' ) || '{}' );
  308. if ( data.version !== REVISION ||
  309. data.settings && ( data.settings.storage === 'url' && data.settings.url !== location.href ) ) {
  310. localStorage.removeItem( 'threejs-inspector' );
  311. return {};
  312. }
  313. return data[ id ] || {};
  314. }
  315. function setItem( id, state ) {
  316. const data = JSON.parse( localStorage.getItem( 'threejs-inspector' ) || '{}' );
  317. if ( state === null ) {
  318. delete data[ id ];
  319. } else {
  320. data[ id ] = state;
  321. }
  322. data.settings = data.settings || {};
  323. data.settings.url = data.settings.url || location.href;
  324. data.settings.storage = data.settings.storage || 'url';
  325. data.version = REVISION;
  326. localStorage.setItem( 'threejs-inspector', JSON.stringify( data ) );
  327. }
  328. export { Inspector, getItem, setItem };
粤ICP备19079148号