three
Version:
JavaScript 3D library
426 lines (236 loc) • 7.2 kB
JavaScript
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;
}
}