@@ -35,6 +35,9 @@ export type FpsStats = {
3535 minFrameMs : number ;
3636 maxFrameMs : number ;
3737 mbPerSec : number ;
38+ avgRenderMs : number ;
39+ maxRenderMs : number ;
40+ transportMode : "webgpu" | "canvas2d" | "worker" | "pending" ;
3841} ;
3942
4043let globalFpsStatsGetter : ( ( ) => FpsStats ) | null = null ;
@@ -347,6 +350,7 @@ export function createImageDataWS(
347350 const totalSize = ySize + uvSize ;
348351
349352 const frameData = new Uint8ClampedArray ( buffer , 0 , totalSize ) ;
353+ const renderStart = performance . now ( ) ;
350354
351355 if ( directCanvas . width !== width || directCanvas . height !== height ) {
352356 directCanvas . width = width ;
@@ -362,7 +366,7 @@ export function createImageDataWS(
362366 ) ;
363367
364368 storeRenderedFrame ( frameData , width , height , yStride , true ) ;
365- actualRendersCount ++ ;
369+ recordRender ( performance . now ( ) - renderStart , "webgpu" ) ;
366370 onmessage ( { width, height } ) ;
367371 }
368372 }
@@ -396,6 +400,7 @@ export function createImageDataWS(
396400 if ( strideBytes === 0 || buffer . byteLength - 24 < frameDataSize ) return ;
397401
398402 const frameData = new Uint8ClampedArray ( buffer , 0 , frameDataSize ) ;
403+ const renderStart = performance . now ( ) ;
399404
400405 if ( directCanvas . width !== width || directCanvas . height !== height ) {
401406 directCanvas . width = width ;
@@ -409,9 +414,9 @@ export function createImageDataWS(
409414 height ,
410415 strideBytes ,
411416 ) ;
412- actualRendersCount ++ ;
413417
414418 storeRenderedFrame ( frameData , width , height , strideBytes , false ) ;
419+ recordRender ( performance . now ( ) - renderStart , "webgpu" ) ;
415420 onmessage ( { width, height } ) ;
416421 }
417422 }
@@ -434,6 +439,8 @@ export function createImageDataWS(
434439 ) {
435440 if ( ! directCanvas || ! directCtx ) return ;
436441
442+ const renderStart = performance . now ( ) ;
443+
437444 if ( directCanvas . width !== width || directCanvas . height !== height ) {
438445 directCanvas . width = width ;
439446 directCanvas . height = height ;
@@ -454,7 +461,7 @@ export function createImageDataWS(
454461 directCtx . putImageData ( cachedDirectImageData , 0 , 0 ) ;
455462
456463 storeRenderedFrame ( frameData , width , height , yStride , true ) ;
457- actualRendersCount ++ ;
464+ recordRender ( performance . now ( ) - renderStart , "canvas2d" ) ;
458465 onmessage ( { width, height } ) ;
459466 }
460467
@@ -560,6 +567,7 @@ export function createImageDataWS(
560567 return ;
561568
562569 const { buffer, width, height } = e . data ;
570+ const renderStart = performance . now ( ) ;
563571 if ( directCanvas . width !== width || directCanvas . height !== height ) {
564572 directCanvas . width = width ;
565573 directCanvas . height = height ;
@@ -585,6 +593,7 @@ export function createImageDataWS(
585593 width * 4 ,
586594 false ,
587595 ) ;
596+ recordRender ( performance . now ( ) - renderStart , "canvas2d" ) ;
588597 onmessage ( { width, height } ) ;
589598 } ;
590599 }
@@ -667,7 +676,7 @@ export function createImageDataWS(
667676 if ( e . data . type === "frame-rendered" ) {
668677 const { width, height } = e . data ;
669678 onmessage ( { width, height } ) ;
670- actualRendersCount ++ ;
679+ recordRender ( 0 , "worker" ) ;
671680 if ( ! hasRenderedFrame ( ) ) {
672681 setHasRenderedFrame ( true ) ;
673682 }
@@ -741,17 +750,52 @@ export function createImageDataWS(
741750 let actualRendersCount = 0 ;
742751 let minFrameTime = Number . MAX_VALUE ;
743752 let maxFrameTime = 0 ;
744- const getLocalFpsStats = ( ) : FpsStats => ( {
745- fps :
746- frameCount > 0 && frameTimeSum > 0
747- ? 1000 / ( frameTimeSum / frameCount )
748- : 0 ,
749- renderFps : actualRendersCount ,
750- avgFrameMs : frameCount > 0 ? frameTimeSum / frameCount : 0 ,
751- minFrameMs : minFrameTime === Number . MAX_VALUE ? 0 : minFrameTime ,
752- maxFrameMs : maxFrameTime ,
753- mbPerSec : totalBytesReceived / 1_000_000 ,
754- } ) ;
753+ let renderTimeSum = 0 ;
754+ let renderTimeCount = 0 ;
755+ let maxRenderMs = 0 ;
756+ let statsWindowStartedAt = performance . now ( ) ;
757+ let transportMode : FpsStats [ "transportMode" ] = "pending" ;
758+
759+ function recordRender ( durationMs : number , mode : FpsStats [ "transportMode" ] ) {
760+ transportMode = mode ;
761+ actualRendersCount ++ ;
762+ if ( durationMs > 0 ) {
763+ renderTimeSum += durationMs ;
764+ renderTimeCount ++ ;
765+ maxRenderMs = Math . max ( maxRenderMs , durationMs ) ;
766+ }
767+ }
768+
769+ const resetStatsWindow = ( now : number ) => {
770+ frameCount = 0 ;
771+ frameTimeSum = 0 ;
772+ totalBytesReceived = 0 ;
773+ actualRendersCount = 0 ;
774+ minFrameTime = Number . MAX_VALUE ;
775+ maxFrameTime = 0 ;
776+ renderTimeSum = 0 ;
777+ renderTimeCount = 0 ;
778+ maxRenderMs = 0 ;
779+ statsWindowStartedAt = now ;
780+ } ;
781+
782+ const getLocalFpsStats = ( ) : FpsStats => {
783+ const elapsedSecs = Math . max (
784+ ( performance . now ( ) - statsWindowStartedAt ) / 1000 ,
785+ 0.001 ,
786+ ) ;
787+ return {
788+ fps : frameCount / elapsedSecs ,
789+ renderFps : actualRendersCount / elapsedSecs ,
790+ avgFrameMs : frameCount > 0 ? frameTimeSum / frameCount : 0 ,
791+ minFrameMs : minFrameTime === Number . MAX_VALUE ? 0 : minFrameTime ,
792+ maxFrameMs : maxFrameTime ,
793+ mbPerSec : totalBytesReceived / 1_000_000 / elapsedSecs ,
794+ avgRenderMs : renderTimeCount > 0 ? renderTimeSum / renderTimeCount : 0 ,
795+ maxRenderMs,
796+ transportMode,
797+ } ;
798+ } ;
755799
756800 globalFpsStatsGetter = getLocalFpsStats ;
757801 ( globalThis as Record < string , unknown > ) . __capFpsStats = getLocalFpsStats ;
@@ -762,6 +806,9 @@ export function createImageDataWS(
762806 ws . onmessage = ( event ) => {
763807 const buffer = event . data as ArrayBuffer ;
764808 const now = performance . now ( ) ;
809+ if ( now - statsWindowStartedAt >= 1000 ) {
810+ resetStatsWindow ( now ) ;
811+ }
765812 totalBytesReceived += buffer . byteLength ;
766813
767814 let isNv12Format = false ;
@@ -776,15 +823,6 @@ export function createImageDataWS(
776823 frameTimeSum += delta ;
777824 minFrameTime = Math . min ( minFrameTime , delta ) ;
778825 maxFrameTime = Math . max ( maxFrameTime , delta ) ;
779-
780- if ( frameCount % 60 === 0 ) {
781- frameCount = 0 ;
782- frameTimeSum = 0 ;
783- totalBytesReceived = 0 ;
784- actualRendersCount = 0 ;
785- minFrameTime = Number . MAX_VALUE ;
786- maxFrameTime = 0 ;
787- }
788826 }
789827 lastFrameTime = now ;
790828
@@ -892,27 +930,13 @@ export function createImageDataWS(
892930 const expectedRowBytes = width * 4 ;
893931 const needsStrideCorrection = strideBytes !== expectedRowBytes ;
894932
895- if ( lastFrameTime > 0 ) {
896- const delta = now - lastFrameTime ;
897- frameCount ++ ;
898- frameTimeSum += delta ;
899- minFrameTime = Math . min ( minFrameTime , delta ) ;
900- maxFrameTime = Math . max ( maxFrameTime , delta ) ;
901- if ( frameCount % 60 === 0 ) {
902- frameTimeSum = 0 ;
903- totalBytesReceived = 0 ;
904- minFrameTime = Number . MAX_VALUE ;
905- maxFrameTime = 0 ;
906- }
907- }
908- lastFrameTime = now ;
909-
910933 if ( ! needsStrideCorrection ) {
911934 const frameData = new Uint8ClampedArray (
912935 buffer ,
913936 0 ,
914937 expectedRowBytes * height ,
915938 ) ;
939+ const renderStart = performance . now ( ) ;
916940
917941 if (
918942 directCanvas . width !== width ||
@@ -941,6 +965,7 @@ export function createImageDataWS(
941965 width * 4 ,
942966 false ,
943967 ) ;
968+ recordRender ( performance . now ( ) - renderStart , "canvas2d" ) ;
944969 onmessage ( { width, height } ) ;
945970 } else {
946971 strideWorker . postMessage (
0 commit comments