Inspector.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. import { RendererInspector } from './RendererInspector.js';
  2. import { Profiler } from './ui/Profiler.js';
  3. import { Performance } from './tabs/Performance.js';
  4. import { Console } from './tabs/Console.js';
  5. import { Parameters } from './tabs/Parameters.js';
  6. import { Viewer } from './tabs/Viewer.js';
  7. import { setText, splitPath, splitCamelCase } from './ui/utils.js';
  8. import { QuadMesh, NodeMaterial, CanvasTarget, setConsoleFunction, REVISION, NoToneMapping } from 'three/webgpu';
  9. import { renderOutput, vec3, vec4 } from 'three/tsl';
  10. class Inspector extends RendererInspector {
  11. constructor() {
  12. super();
  13. // init profiler
  14. const profiler = new Profiler();
  15. const parameters = new Parameters();
  16. parameters.hide();
  17. profiler.addTab( parameters );
  18. const viewer = new Viewer();
  19. viewer.hide();
  20. profiler.addTab( viewer );
  21. const performance = new Performance();
  22. profiler.addTab( performance );
  23. const console = new Console();
  24. profiler.addTab( console );
  25. profiler.setActiveTab( performance.id );
  26. //
  27. this.statsData = new Map();
  28. this.canvasNodes = new Map();
  29. this.profiler = profiler;
  30. this.performance = performance;
  31. this.console = console;
  32. this.parameters = parameters;
  33. this.viewer = viewer;
  34. this.once = {};
  35. this.displayCycle = {
  36. text: {
  37. needsUpdate: false,
  38. duration: .25,
  39. time: 0
  40. },
  41. graph: {
  42. needsUpdate: false,
  43. duration: .02,
  44. time: 0
  45. }
  46. };
  47. }
  48. get domElement() {
  49. return this.profiler.domElement;
  50. }
  51. resolveConsoleOnce( type, message ) {
  52. const key = type + message;
  53. if ( this.once[ key ] !== true ) {
  54. this.resolveConsole( type, message );
  55. this.once[ key ] = true;
  56. }
  57. }
  58. resolveConsole( type, message ) {
  59. switch ( type ) {
  60. case 'log':
  61. this.console.addMessage( 'info', message );
  62. console.log( message );
  63. break;
  64. case 'warn':
  65. this.console.addMessage( 'warn', message );
  66. console.warn( message );
  67. break;
  68. case 'error':
  69. this.console.addMessage( 'error', message );
  70. console.error( message );
  71. break;
  72. }
  73. }
  74. init() {
  75. const renderer = this.getRenderer();
  76. let sign = `THREE.WebGPURenderer: ${ REVISION } [ "`;
  77. if ( renderer.backend.isWebGPUBackend ) {
  78. sign += 'WebGPU';
  79. } else if ( renderer.backend.isWebGLBackend ) {
  80. sign += 'WebGL2';
  81. }
  82. sign += '" ]';
  83. this.console.addMessage( 'info', sign );
  84. //
  85. if ( renderer.inspector.domElement.parentElement === null && renderer.domElement.parentElement !== null ) {
  86. renderer.domElement.parentElement.appendChild( renderer.inspector.domElement );
  87. }
  88. }
  89. setRenderer( renderer ) {
  90. super.setRenderer( renderer );
  91. if ( renderer !== null ) {
  92. setConsoleFunction( this.resolveConsole.bind( this ) );
  93. if ( this.isAvailable ) {
  94. renderer.backend.trackTimestamp = true;
  95. renderer.init().then( () => {
  96. if ( renderer.hasFeature( 'timestamp-query' ) !== true ) {
  97. this.console.addMessage( 'error', 'THREE.Inspector: GPU Timestamp Queries not available.' );
  98. }
  99. } );
  100. }
  101. }
  102. return this;
  103. }
  104. createParameters( name ) {
  105. if ( this.parameters.isVisible === false ) {
  106. this.parameters.show();
  107. this.profiler.setActiveTab( this.parameters.id );
  108. }
  109. return this.parameters.createGroup( name );
  110. }
  111. getStatsData( cid ) {
  112. let data = this.statsData.get( cid );
  113. if ( data === undefined ) {
  114. data = {};
  115. this.statsData.set( cid, data );
  116. }
  117. return data;
  118. }
  119. resolveStats( stats ) {
  120. const data = this.getStatsData( stats.cid );
  121. if ( data.initialized !== true ) {
  122. data.cpu = stats.cpu;
  123. data.gpu = stats.gpu;
  124. data.stats = [];
  125. data.initialized = true;
  126. }
  127. // store stats
  128. if ( data.stats.length > this.maxFrames ) {
  129. data.stats.shift();
  130. }
  131. data.stats.push( stats );
  132. // compute averages
  133. data.cpu = this.getAverageDeltaTime( data, 'cpu' );
  134. data.gpu = this.getAverageDeltaTime( data, 'gpu' );
  135. data.total = data.cpu + data.gpu;
  136. // children
  137. for ( const child of stats.children ) {
  138. this.resolveStats( child );
  139. const childData = this.getStatsData( child.cid );
  140. data.cpu += childData.cpu;
  141. data.gpu += childData.gpu;
  142. data.total += childData.total;
  143. }
  144. }
  145. getCanvasDataByNode( node ) {
  146. let canvasData = this.canvasNodes.get( node );
  147. if ( canvasData === undefined ) {
  148. const renderer = this.getRenderer();
  149. const canvas = document.createElement( 'canvas' );
  150. const canvasTarget = new CanvasTarget( canvas );
  151. canvasTarget.setPixelRatio( window.devicePixelRatio );
  152. canvasTarget.setSize( 140, 140 );
  153. const id = node.id;
  154. const { path, name } = splitPath( splitCamelCase( node.getName() || '(unnamed)' ) );
  155. let output = vec4( vec3( node ), 1 );
  156. output = renderOutput( output, NoToneMapping, renderer.outputColorSpace );
  157. output = output.context( { inspector: true } );
  158. const material = new NodeMaterial();
  159. material.outputNode = output;
  160. const quad = new QuadMesh( material );
  161. quad.name = 'Viewer - ' + name;
  162. canvasData = {
  163. id,
  164. name,
  165. path,
  166. node,
  167. quad,
  168. canvasTarget,
  169. material
  170. };
  171. this.canvasNodes.set( node, canvasData );
  172. }
  173. return canvasData;
  174. }
  175. resolveViewer() {
  176. const nodes = this.currentNodes;
  177. const renderer = this.getRenderer();
  178. if ( nodes.length === 0 ) return;
  179. if ( ! renderer.backend.isWebGPUBackend ) {
  180. this.resolveConsoleOnce( 'warn', 'Inspector: Viewer is only available with WebGPU.' );
  181. return;
  182. }
  183. //
  184. if ( ! this.viewer.isVisible ) {
  185. this.viewer.show();
  186. }
  187. const canvasDataList = nodes.map( node => this.getCanvasDataByNode( node ) );
  188. this.viewer.update( renderer, canvasDataList );
  189. }
  190. getAverageDeltaTime( statsData, property, frames = this.fps ) {
  191. const statsArray = statsData.stats;
  192. let sum = 0;
  193. let count = 0;
  194. for ( let i = statsArray.length - 1; i >= 0 && count < frames; i -- ) {
  195. const stats = statsArray[ i ];
  196. const value = stats[ property ];
  197. if ( value > 0 ) {
  198. // ignore invalid values
  199. sum += value;
  200. count ++;
  201. }
  202. }
  203. return sum / count;
  204. }
  205. resolveFrame( frame ) {
  206. const nextFrame = this.getFrameById( frame.frameId + 1 );
  207. if ( ! nextFrame ) return;
  208. frame.cpu = 0;
  209. frame.gpu = 0;
  210. frame.total = 0;
  211. for ( const stats of frame.children ) {
  212. this.resolveStats( stats );
  213. const data = this.getStatsData( stats.cid );
  214. frame.cpu += data.cpu;
  215. frame.gpu += data.gpu;
  216. frame.total += data.total;
  217. }
  218. // improve stats using next frame
  219. frame.deltaTime = nextFrame.startTime - frame.startTime;
  220. frame.miscellaneous = frame.deltaTime - frame.total;
  221. if ( frame.miscellaneous < 0 ) {
  222. // Frame desync, probably due to async GPU timing.
  223. frame.miscellaneous = 0;
  224. }
  225. //
  226. this.updateCycle( this.displayCycle.text );
  227. this.updateCycle( this.displayCycle.graph );
  228. if ( this.displayCycle.text.needsUpdate ) {
  229. setText( 'fps-counter', this.fps.toFixed() );
  230. this.performance.updateText( this, frame );
  231. }
  232. if ( this.displayCycle.graph.needsUpdate ) {
  233. this.performance.updateGraph( this, frame );
  234. }
  235. this.displayCycle.text.needsUpdate = false;
  236. this.displayCycle.graph.needsUpdate = false;
  237. }
  238. updateCycle( cycle ) {
  239. cycle.time += this.nodeFrame.deltaTime;
  240. if ( cycle.time >= cycle.duration ) {
  241. cycle.needsUpdate = true;
  242. cycle.time = 0;
  243. }
  244. }
  245. }
  246. export { Inspector };
粤ICP备19079148号