UNPKG

three

Version:

JavaScript 3D library

426 lines (236 loc) 7.2 kB
import { InspectorBase, TimestampQuery, warnOnce } from 'three/webgpu'; class ObjectStats { constructor( uid, name ) { this.uid = uid; this.cid = uid.match( /^(.*):f(\d+)$/ )[ 1 ]; // call id this.name = name; this.timestamp = 0; this.cpu = 0; this.gpu = 0; this.fps = 0; this.children = []; this.parent = null; } } class RenderStats extends ObjectStats { constructor( uid, scene, camera, renderTarget ) { let name = scene.name; if ( name === '' ) { if ( scene.isScene ) { name = 'Scene'; } else if ( scene.isQuadMesh ) { name = 'QuadMesh'; } } super( uid, name ); this.scene = scene; this.camera = camera; this.renderTarget = renderTarget; this.isRenderStats = true; } } class ComputeStats extends ObjectStats { constructor( uid, computeNode ) { super( uid, computeNode.name ); this.computeNode = computeNode; this.isComputeStats = true; } } export class RendererInspector extends InspectorBase { constructor() { super(); this.currentFrame = null; this.currentRender = null; this.currentNodes = null; this.lastFrame = null; this.frames = []; this.framesLib = {}; this.maxFrames = 512; this._lastFinishTime = 0; this._resolveTimestampPromise = null; this.isRendererInspector = true; } getParent() { return this.currentRender || this.getFrame(); } begin() { this.currentFrame = this._createFrame(); this.currentRender = this.currentFrame; this.currentNodes = []; } finish() { const now = performance.now(); const frame = this.currentFrame; frame.finishTime = now; frame.deltaTime = now - ( this._lastFinishTime > 0 ? this._lastFinishTime : now ); this.addFrame( frame ); this.fps = this._getFPS(); this.lastFrame = frame; this.currentFrame = null; this.currentRender = null; this.currentNodes = null; this._lastFinishTime = now; } _getFPS() { let frameSum = 0; let timeSum = 0; for ( let i = this.frames.length - 1; i >= 0; i -- ) { const frame = this.frames[ i ]; frameSum ++; timeSum += frame.deltaTime; if ( timeSum >= 1000 ) break; } return ( frameSum * 1000 ) / timeSum; } _createFrame() { return { frameId: this.nodeFrame.frameId, resolvedCompute: false, resolvedRender: false, deltaTime: 0, startTime: performance.now(), finishTime: 0, miscellaneous: 0, children: [], renders: [], computes: [] }; } getFrame() { return this.currentFrame || this.lastFrame; } getFrameById( frameId ) { return this.framesLib[ frameId ] || null; } resolveViewer() { } resolveFrame( /*frame*/ ) { } async resolveTimestamp() { if ( this._resolveTimestampPromise !== null ) { return this._resolveTimestampPromise; } this._resolveTimestampPromise = new Promise( ( resolve ) => { requestAnimationFrame( async () => { const renderer = this.getRenderer(); await renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE ); await renderer.resolveTimestampsAsync( TimestampQuery.RENDER ); const computeFrames = renderer.backend.getTimestampFrames( TimestampQuery.COMPUTE ); const renderFrames = renderer.backend.getTimestampFrames( TimestampQuery.RENDER ); const frameIds = [ ...new Set( [ ...computeFrames, ...renderFrames ] ) ]; for ( const frameId of frameIds ) { const frame = this.getFrameById( frameId ); if ( frame !== null ) { // resolve compute timestamps if ( frame.resolvedCompute === false ) { if ( frame.computes.length > 0 ) { if ( computeFrames.includes( frameId ) ) { for ( const stats of frame.computes ) { if ( renderer.backend.hasTimestamp( stats.uid ) ) { stats.gpu = renderer.backend.getTimestamp( stats.uid ); } else { stats.gpu = 0; stats.gpuNotAvailable = true; } } frame.resolvedCompute = true; } } else { frame.resolvedCompute = true; } } // resolve render timestamps if ( frame.resolvedRender === false ) { if ( frame.renders.length > 0 ) { if ( renderFrames.includes( frameId ) ) { for ( const stats of frame.renders ) { if ( renderer.backend.hasTimestamp( stats.uid ) ) { stats.gpu = renderer.backend.getTimestamp( stats.uid ); } else { stats.gpu = 0; stats.gpuNotAvailable = true; } } frame.resolvedRender = true; } } else { frame.resolvedRender = true; } } if ( frame.resolvedCompute === true && frame.resolvedRender === true ) { this.resolveFrame( frame ); } } } this._resolveTimestampPromise = null; resolve(); } ); } ); return this._resolveTimestampPromise; } get isAvailable() { const renderer = this.getRenderer(); return renderer !== null; } addFrame( frame ) { // Limit to max frames. if ( this.frames.length >= this.maxFrames ) { const removedFrame = this.frames.shift(); delete this.framesLib[ removedFrame.frameId ]; } this.frames.push( frame ); this.framesLib[ frame.frameId ] = frame; if ( this.isAvailable ) { this.resolveViewer(); this.resolveTimestamp(); } } inspect( node ) { const currentNodes = this.currentNodes; if ( currentNodes !== null ) { currentNodes.push( node ); } else { warnOnce( 'RendererInspector: Unable to inspect node outside of frame scope. Use "renderer.setAnimationLoop()".' ); } } beginCompute( uid, computeNode ) { const frame = this.getFrame(); if ( ! frame ) return; const currentCompute = new ComputeStats( uid, computeNode ); currentCompute.timestamp = performance.now(); currentCompute.parent = this.currentCompute || this.getParent(); frame.computes.push( currentCompute ); if ( this.currentRender !== null ) { this.currentRender.children.push( currentCompute ); } else { frame.children.push( currentCompute ); } this.currentCompute = currentCompute; } finishCompute() { const frame = this.getFrame(); if ( ! frame ) return; const currentCompute = this.currentCompute; currentCompute.cpu = performance.now() - currentCompute.timestamp; this.currentCompute = currentCompute.parent.isComputeStats ? currentCompute.parent : null; } beginRender( uid, scene, camera, renderTarget ) { const frame = this.getFrame(); if ( ! frame ) return; const currentRender = new RenderStats( uid, scene, camera, renderTarget ); currentRender.timestamp = performance.now(); currentRender.parent = this.getParent(); frame.renders.push( currentRender ); if ( this.currentRender !== null ) { this.currentRender.children.push( currentRender ); } else { frame.children.push( currentRender ); } this.currentRender = currentRender; } finishRender() { const frame = this.getFrame(); if ( ! frame ) return; const currentRender = this.currentRender; currentRender.cpu = performance.now() - currentRender.timestamp; this.currentRender = currentRender.parent; } }