Inspector.js 9.0 KB

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