import { FC , HTMLAttributes , useEffect , useRef } from 'react' ; import { addEffect , addAfterEffect , useThree , addTail , } from '@react-three/fiber' ; import GLPerf from './perf' ; import create from 'zustand' ; import { PerfProps } from '.' ; import { Material , Mesh , Points , Scene , WebGLProgram , WebGLRenderer , } from 'three' ; import countGeoDrawCalls from './helpers/countGeoDrawCalls' ; type drawCount = { type : string ; drawCount : number ; } ; export type drawCounts = { total : number ; type : string ; data : drawCount [ ] ; } ; export type ProgramsPerf = { meshes ? : { [ index ] : Mesh [ ] ; } ; material : Material ; program ? : WebGLProgram ; visible : boolean ; drawCounts : drawCounts ; expand : boolean ; } ; export type ProgramsPerfs = Map < string , ProgramsPerf > ; const isUUID = ( uuid ) => { let s = '' + uuid ; s = s . match ( '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' ) ; if ( s === null ) { return false ; } return true ; } ; const addMuiPerfID = ( material , currentObjectWithMaterials ) => { if ( ! material . defines ) { material . defines = { } ; } if ( material . defines && ! material . defines . muiPerf ) { material . defines = Object . assign ( material . defines || { } , { muiPerf : material . uuid } ) ; } const uuid = material . uuid ; if ( ! currentObjectWithMaterials [ uuid ] ) { currentObjectWithMaterials [ uuid ] = { meshes : { } , material : material } ; material . needsUpdate = true ; } material . needsUpdate = false ; return uuid ; } ; export type State = { log : any ; paused : boolean ; triggerProgramsUpdate : number ; chart : { data : { [ index ] : number [ ] ; } ; circularId : number ; } ; gl : WebGLRenderer | undefined ; scene : Scene | undefined ; programs : ProgramsPerfs ; objectWithMaterials : Mesh [ ] | null ; tab : 'infos' | 'programs' | 'data' ; } ; let PerfLib ; type Logger = { i : number ; cpu : number ; gpu : number ; mem : number ; fps : number ; duration : number ; frameCount : number ; } ; type Chart = { data : { [ index ] : number [ ] ; } ; id : number ; circularId : number ; } ; const getMUIIndex = ( muid ) => muid === 'muiPerf' ; export const usePerfStore = create < State > ( ( ) => ( { log : null , paused : false , triggerProgramsUpdate : 0 , chart : { data : { fps : [ ] , gpu : [ ] , cpu : [ ] } , circularId : 0 } , gl : undefined , objectWithMaterials : null , scene : undefined , programs : new Map ( ) , sceneLength : undefined , tab : 'infos' } ) ) ; export const usePerfFunc = ( ) => { return { log : usePerfStore ( ( state ) => state . log ) , gl : usePerfStore ( ( state ) => state . gl ) ?. info , programs : usePerfStore ( ( state ) => state . programs ) } ; } ; export interface Props extends HTMLAttributes < HTMLDivElement > { } /**
 * Performance profiler component
 */ export const Headless = ( { trackGPU , trackCPU , chart } ) => { const { gl , scene } = useThree ( ) ; const mounted = useRef ( false ) ; usePerfStore . setState ( { gl , scene } ) ; useEffect ( ( ) => { gl . info . autoReset = false ; if ( ! PerfLib && gl . info ) { PerfLib = new GLPerf ( { trackGPU : trackGPU , trackCPU : trackCPU , chartLen : chart ? chart . length : 120 , chartHz : chart ? chart . hz : 60 , gl : gl . getContext ( ) , chartLogger : ( chart ) => { usePerfStore . setState ( { chart } ) ; } , paramLogger : ( logger ) => { if ( PerfLib && gl . info ) { PerfLib . factorGPU = 1 / gl . info . render . calls ; } usePerfStore . setState ( { log : { cpu : logger . cpu , gpu : logger . gpu , mem : logger . mem , fps : logger . fps , totalTime : logger . duration , frameCount : logger . frameCount } } ) ; } } ) ; } if ( PerfLib && gl . info ) { PerfLib . gl = gl . getContext ( ) ; const unsub1 = addEffect ( ( ) => { if ( usePerfStore . getState ( ) . paused ) { usePerfStore . setState ( { paused : false } ) ; } if ( PerfLib && gl . info ) { gl . info . reset ( ) ; PerfLib . begin ( 'profiler' ) ; } return false ; } ) ; const unsub2 = addAfterEffect ( ( ) => { if ( PerfLib ) { PerfLib . end ( 'profiler' ) ; PerfLib . nextFrame ( window . performance . now ( ) ) ; } const currentObjectWithMaterials = { } ; const programs = new Map ( ) ; scene . traverse ( function ( object ) { if ( object instanceof Mesh || object instanceof Points ) { if ( object . material ) { let uuid = object . material . uuid ; const isTroika = Array . isArray ( object . material ) && object . material . length > 1 ; if ( isTroika ) { uuid = addMuiPerfID ( object . material [ 1 ] , currentObjectWithMaterials ) ; } else { uuid = addMuiPerfID ( object . material , currentObjectWithMaterials ) ; } currentObjectWithMaterials [ uuid ] . meshes [ object . uuid ] = object ; } } } ) ; gl ?. info ?. programs ?. forEach ( ( program ) => { const cacheKeySplited = program . cacheKey . split ( ',' ) ; const muiPerfTracker = cacheKeySplited [ cacheKeySplited . findIndex ( getMUIIndex ) + 1 ] ; if ( isUUID ( muiPerfTracker ) && currentObjectWithMaterials [ muiPerfTracker ] ) { const { material , meshes } = currentObjectWithMaterials [ muiPerfTracker ] ; programs . set ( muiPerfTracker , { program , material , meshes , drawCounts : { total : 0 , type : 'triangle' , data : [ ] } , expand : false , visible : true } ) ; } } ) ; if ( programs . size !== usePerfStore . getState ( ) . programs . size ) { countGeoDrawCalls ( programs ) ; usePerfStore . setState ( { programs : programs , triggerProgramsUpdate : usePerfStore . getState ( ) . triggerProgramsUpdate ++ } ) ; } return false ; } ) ; return ( ) => { unsub1 ( ) ; unsub2 ( ) ; } ; } else { return undefined ; } } , [ gl , trackGPU , trackCPU , chart ] ) ; useEffect ( ( ) => { const unsub = addTail ( ( ) => { if ( PerfLib && mounted . current ) { PerfLib . paused = true ; usePerfStore . setState ( { paused : true , log : { cpu : 0 , gpu : 0 , mem : 0 , fps : 0 , totalTime : 0 , frameCount : 0 } } ) ; } mounted . current = true ; return false ; } ) ; return ( ) => { unsub ( ) ; } ; } , [ mounted ] ) ; return null ; } ;