@@ -68,6 +68,8 @@ const delayedStartStats = new Set([
6868 * graphs. Defaults to 1.
6969 * @property {number } [cpuTimingMinSize] - Minimum size index at which to show CPU sub-timing
7070 * graphs (script, anim, physics, render). Defaults to 1.
71+ * @property {number } [vramTimingMinSize] - Minimum size index at which to show VRAM subcategory
72+ * graphs. Defaults to 1.
7173 */
7274
7375/**
@@ -118,10 +120,11 @@ class MiniStats {
118120 this . wordAtlas = new WordAtlas ( device , words ) ;
119121 this . _activeSizeIndex = options . startSizeIndex ;
120122
121- // if GPU pass tracking or CPU timing is enabled, use the last width for medium/large sizes
123+ // if GPU pass tracking, CPU timing or VRAM detail is enabled, use the last width for medium/large sizes
122124 const gpuTimingMinSize = options . gpuTimingMinSize ?? 1 ;
123125 const cpuTimingMinSize = options . cpuTimingMinSize ?? 1 ;
124- if ( gpuTimingMinSize < this . sizes . length || cpuTimingMinSize < this . sizes . length ) {
126+ const vramTimingMinSize = options . vramTimingMinSize ?? 1 ;
127+ if ( gpuTimingMinSize < this . sizes . length || cpuTimingMinSize < this . sizes . length || vramTimingMinSize < this . sizes . length ) {
125128 const lastWidth = this . sizes [ this . sizes . length - 1 ] . width ;
126129 for ( let i = 1 ; i < this . sizes . length - 1 ; i ++ ) {
127130 this . sizes [ i ] . width = lastWidth ;
@@ -177,6 +180,10 @@ class MiniStats {
177180 this . cpuTimingMinSize = cpuTimingMinSize ;
178181 this . cpuGraphs = new Map ( ) ; // Map<statName, { graph, lastNonZeroFrame }>
179182
183+ // VRAM subcategory tracking
184+ this . vramTimingMinSize = vramTimingMinSize ;
185+ this . vramGraphs = new Map ( ) ; // Map<statName, { graph, lastNonZeroFrame }>
186+
180187 this . frameIndex = 0 ;
181188 this . textRefreshRate = options . textRefreshRate ;
182189
@@ -197,6 +204,8 @@ class MiniStats {
197204
198205 this . graphs . forEach ( graph => graph . destroy ( ) ) ;
199206 this . gpuPassGraphs . clear ( ) ;
207+ this . cpuGraphs . clear ( ) ;
208+ this . vramGraphs . clear ( ) ;
200209 this . wordAtlas . destroy ( ) ;
201210 this . texture . destroy ( ) ;
202211 this . div . remove ( ) ;
@@ -210,6 +219,7 @@ class MiniStats {
210219 * - GPU utilization
211220 * - Overall frame time
212221 * - Draw call count
222+ * - Total VRAM usage
213223 *
214224 * @returns {object } The default options for MiniStats.
215225 * @example
@@ -267,14 +277,27 @@ class MiniStats {
267277 name : 'DrawCalls' ,
268278 stats : [ 'drawCalls.total' ] ,
269279 watermark : 1000
280+ } ,
281+
282+ // used VRAM in MB
283+ {
284+ name : 'VRAM' ,
285+ stats : [ 'vram.totalUsed' ] ,
286+ decimalPlaces : 1 ,
287+ multiplier : 1 / ( 1024 * 1024 ) ,
288+ unitsName : 'MB' ,
289+ watermark : 1024
270290 }
271291 ] ,
272292
273293 // minimum size index to show GPU pass timing graphs
274294 gpuTimingMinSize : 1 ,
275295
276296 // minimum size index to show CPU sub-timing graphs
277- cpuTimingMinSize : 1
297+ cpuTimingMinSize : 1 ,
298+
299+ // minimum size index to show VRAM subcategory graphs
300+ vramTimingMinSize : 1
278301 } ;
279302 }
280303
@@ -306,9 +329,9 @@ class MiniStats {
306329 }
307330 this . gpuPassGraphs . clear ( ) ;
308331
309- // reset main GPU graph to default background color
332+ // keep main GPU graph in GPU color group
310333 const gpuGraph = this . graphs . find ( g => g . name === 'GPU' ) ;
311- if ( gpuGraph ) gpuGraph . graphType = 0.0 ;
334+ if ( gpuGraph ) gpuGraph . graphType = 0.33 ;
312335 }
313336
314337 // delete CPU sub-timing graphs when switching below threshold
@@ -323,9 +346,9 @@ class MiniStats {
323346 }
324347 this . cpuGraphs . clear ( ) ;
325348
326- // reset main CPU graph to default background color
349+ // keep main CPU graph in CPU color group
327350 const cpuGraph = this . graphs . find ( g => g . name === 'CPU' ) ;
328- if ( cpuGraph ) cpuGraph . graphType = 0.0 ;
351+ if ( cpuGraph ) cpuGraph . graphType = 0.66 ;
329352 }
330353 }
331354
@@ -407,20 +430,37 @@ class MiniStats {
407430 initGraphs ( app , device , options ) {
408431 this . graphs = [ ] ;
409432
433+ // Add VRAM first so it appears at the bottom in the compact stacked view.
434+ // Graphs are rendered bottom-to-top.
435+ if ( options . stats ) {
436+ options . stats . forEach ( ( entry ) => {
437+ if ( entry . name === 'VRAM' ) {
438+ const timer = new StatsTimer ( app , entry . stats , entry . decimalPlaces , entry . unitsName , entry . multiplier ) ;
439+ const graph = new Graph ( entry . name , app , entry . watermark , options . textRefreshRate , timer ) ;
440+ this . graphs . push ( graph ) ;
441+ }
442+ } ) ;
443+ }
444+
410445 if ( options . cpu . enabled ) {
411446 const timer = new CpuTimer ( app ) ;
412447 const graph = new Graph ( 'CPU' , app , options . cpu . watermark , options . textRefreshRate , timer ) ;
448+ graph . graphType = 0.66 ;
413449 this . graphs . push ( graph ) ;
414450 }
415451
416452 if ( options . gpu . enabled ) {
417453 const timer = new GpuTimer ( device ) ;
418454 const graph = new Graph ( 'GPU' , app , options . gpu . watermark , options . textRefreshRate , timer ) ;
455+ graph . graphType = 0.33 ;
419456 this . graphs . push ( graph ) ;
420457 }
421458
422459 if ( options . stats ) {
423460 options . stats . forEach ( ( entry ) => {
461+ if ( entry . name === 'VRAM' ) {
462+ return ;
463+ }
424464 const timer = new StatsTimer ( app , entry . stats , entry . decimalPlaces , entry . unitsName , entry . multiplier ) ;
425465 const graph = new Graph ( entry . name , app , entry . watermark , options . textRefreshRate , timer ) ;
426466 this . graphs . push ( graph ) ;
@@ -590,6 +630,7 @@ class MiniStats {
590630
591631 // scan for new sub-stats
592632 const statsEntries = ( stats instanceof Map ) ? stats : Object . entries ( stats ) ;
633+ const mainGraph = this . graphs . find ( g => g . name === mainGraphName ) ;
593634 for ( const [ statName , timing ] of statsEntries ) {
594635 if ( ! subGraphs . has ( statName ) ) {
595636 // Skip creating graph for auto-hide stats with zero timing
@@ -607,11 +648,15 @@ class MiniStats {
607648 }
608649 const graphName = ` ${ displayName } ` ; // indent with 2 spaces
609650
610- // initial watermark (will be synced to main graph)
611- const watermark = 10.0 ;
651+ // use main graph watermark when available
652+ const watermark = mainGraph ?. watermark ?? 10.0 ;
653+
654+ const decimalPlaces = 1 ;
655+ const unitsName = statPathPrefix === 'vram' ? 'MB' : 'ms' ;
656+ const multiplier = statPathPrefix === 'vram' ? 1 / ( 1024 * 1024 ) : 1 ;
612657
613658 const statPath = `${ statPathPrefix } .${ statName } ` ;
614- const timer = new StatsTimer ( this . app , [ statPath ] , 1 , 'ms' , 1 ) ;
659+ const timer = new StatsTimer ( this . app , [ statPath ] , decimalPlaces , unitsName , multiplier ) ;
615660 const graph = new Graph ( graphName , this . app , watermark , this . textRefreshRate , timer ) ;
616661
617662 // Set graph type for background tinting
@@ -656,23 +701,10 @@ class MiniStats {
656701 }
657702
658703 // sync all sub-stat watermarks to match main graph
659- const mainGraph = this . graphs . find ( g => g . name === mainGraphName ) ;
660704 if ( mainGraph ) {
661705 for ( const statData of subGraphs . values ( ) ) {
662706 statData . graph . watermark = mainGraph . watermark ;
663707 }
664-
665- // set main graph background color to match sub-graphs when they exist
666- if ( subGraphs . size > 0 ) {
667- if ( statPathPrefix === 'gpu' ) {
668- mainGraph . graphType = 0.33 ; // Match GPU sub-graphs
669- } else if ( statPathPrefix === 'frame' ) {
670- mainGraph . graphType = 0.66 ; // Match CPU sub-graphs
671- }
672- } else {
673- // reset to default background when no sub-graphs
674- mainGraph . graphType = 0.0 ;
675- }
676708 }
677709 }
678710
@@ -757,6 +789,19 @@ class MiniStats {
757789 } ;
758790 this . updateSubStats ( this . cpuGraphs , 'CPU' , cpuStats , 'frame' , 240 ) ;
759791 }
792+
793+ // Update VRAM subcategory graphs when size index meets threshold
794+ if ( this . _activeSizeIndex >= this . vramTimingMinSize ) {
795+ const vram = this . app . stats . vram ;
796+ const vramStats = {
797+ tex : vram . tex ,
798+ geom : vram . geom
799+ } ;
800+ if ( this . device . isWebGPU ) {
801+ vramStats . buffers = vram . buffers ;
802+ }
803+ this . updateSubStats ( this . vramGraphs , 'VRAM' , vramStats , 'vram' , 0 ) ;
804+ }
760805 }
761806
762807 this . frameIndex ++ ;
0 commit comments