@@ -9,6 +9,11 @@ export function WebGLShader() {
99 const lastFrameTimeRef = useRef < number > ( 0 ) ;
1010 const framesRenderedRef = useRef < number > ( 0 ) ;
1111 const lastFpsCalculateTimeRef = useRef < number > ( 0 ) ;
12+ const totalFramesRef = useRef < number > ( 0 ) ;
13+ const degradationEventsRef = useRef < number > ( 0 ) ;
14+ const shaderStartTimeRef = useRef < number > ( 0 ) ;
15+ const peakFpsRef = useRef < number > ( 0 ) ;
16+ const minFpsRef = useRef < number > ( Infinity ) ;
1217
1318 const [ isVisible , setIsVisible ] = useState ( true ) ;
1419 const [ prefersReducedMotion , setPrefersReducedMotion ] = useState ( false ) ;
@@ -17,20 +22,42 @@ export function WebGLShader() {
1722
1823 // Stats state
1924 const [ stats , setStats ] = useState ( {
25+ // -- Performance --
2026 fps : 0 ,
27+ peakFps : 0 ,
28+ minFps : 0 ,
2129 targetFps : "Uncapped (Native)" ,
30+ frameTimeMs : 0 ,
31+ totalFrames : 0 ,
32+ uptime : "0s" ,
33+ // -- Rendering --
2234 dprMultiplier : 1.5 ,
2335 actualDpr : 1 ,
36+ nativeDpr : 1 ,
2437 logicalWidth : 0 ,
2538 logicalHeight : 0 ,
2639 canvasWidth : 0 ,
2740 canvasHeight : 0 ,
28- frameTimeMs : 0 ,
41+ // -- Hardware --
2942 concurrency : 4 ,
3043 memory : 4 ,
3144 renderer : "Unknown" ,
3245 vendor : "Unknown" ,
46+ platform : "Unknown" ,
47+ touchPoints : 0 ,
48+ colorDepth : 24 ,
49+ // -- WebGL --
50+ webglVersion : "WebGL 1.0" ,
51+ maxTextureSize : 0 ,
52+ maxViewportDims : "0x0" ,
53+ shaderPrecision : "Unknown" ,
54+ extensionsCount : 0 ,
55+ // -- Network --
56+ connectionType : "Unknown" ,
57+ // -- State --
3358 degraded : false ,
59+ degradationEvents : 0 ,
60+ reducedMotion : false ,
3461 } ) ;
3562
3663 const fpsRef = useRef < number > ( 0 ) ; // 0 means uncapped
@@ -105,9 +132,43 @@ export function WebGLShader() {
105132 return ;
106133 }
107134
135+ // --- Collect GPU & WebGL stats ---
108136 const rendererDebugInfo = gl . getExtension ( "WEBGL_debug_renderer_info" ) ;
109137 const renderer = rendererDebugInfo ? gl . getParameter ( rendererDebugInfo . UNMASKED_RENDERER_WEBGL ) : gl . getParameter ( gl . RENDERER ) ;
110138 const vendor = rendererDebugInfo ? gl . getParameter ( rendererDebugInfo . UNMASKED_VENDOR_WEBGL ) : gl . getParameter ( gl . VENDOR ) ;
139+ const maxTextureSize = gl . getParameter ( gl . MAX_TEXTURE_SIZE ) ;
140+ const maxViewportDims = gl . getParameter ( gl . MAX_VIEWPORT_DIMS ) ;
141+ const extensionsCount = ( gl . getSupportedExtensions ( ) || [ ] ) . length ;
142+
143+ // Shader precision
144+ let shaderPrecision = "Unknown" ;
145+ try {
146+ const hp = gl . getShaderPrecisionFormat ( gl . FRAGMENT_SHADER , gl . HIGH_FLOAT ) ;
147+ const mp = gl . getShaderPrecisionFormat ( gl . FRAGMENT_SHADER , gl . MEDIUM_FLOAT ) ;
148+ if ( hp && hp . precision > 0 ) shaderPrecision = `highp (${ hp . precision } -bit)` ;
149+ else if ( mp && mp . precision > 0 ) shaderPrecision = `mediump (${ mp . precision } -bit)` ;
150+ else shaderPrecision = "lowp" ;
151+ } catch { shaderPrecision = "N/A" ; }
152+
153+ // Platform info
154+ const platform = typeof navigator !== "undefined" ? ( navigator . platform || "Unknown" ) : "Unknown" ;
155+ const touchPoints = typeof navigator !== "undefined" ? ( navigator . maxTouchPoints || 0 ) : 0 ;
156+ const colorDepth = typeof screen !== "undefined" ? screen . colorDepth : 24 ;
157+ const nativeDpr = typeof window !== "undefined" ? window . devicePixelRatio : 1 ;
158+
159+ // Connection info
160+ let connectionType = "Unknown" ;
161+ if ( typeof navigator !== "undefined" ) {
162+ // @ts -expect-error connection is non-standard
163+ const conn = navigator . connection || navigator . mozConnection || navigator . webkitConnection ;
164+ if ( conn ) {
165+ connectionType = conn . effectiveType || conn . type || "Unknown" ;
166+ }
167+ }
168+
169+ shaderStartTimeRef . current = performance . now ( ) ;
170+ peakFpsRef . current = 0 ;
171+ minFpsRef . current = Infinity ;
111172
112173 let currentPixelRatio = Math . min ( window . devicePixelRatio , dprMultiplier ) ;
113174
@@ -203,6 +264,14 @@ export function WebGLShader() {
203264
204265 let time = 0 ;
205266
267+ // Format uptime
268+ const formatUptime = ( ) => {
269+ const elapsed = Math . floor ( ( performance . now ( ) - shaderStartTimeRef . current ) / 1000 ) ;
270+ const m = Math . floor ( elapsed / 60 ) ;
271+ const s = elapsed % 60 ;
272+ return m > 0 ? `${ m } m ${ s } s` : `${ s } s` ;
273+ } ;
274+
206275 // Stats update helper
207276 const updateStats = ( w : number , h : number , lw : number , lh : number , isDegraded = false ) => {
208277 setStats ( s => ( {
@@ -215,12 +284,26 @@ export function WebGLShader() {
215284 memory,
216285 renderer,
217286 vendor,
287+ platform,
288+ touchPoints,
289+ colorDepth,
290+ nativeDpr,
218291 dprMultiplier,
219292 actualDpr : currentPixelRatio ,
293+ webglVersion : "WebGL 1.0" ,
294+ maxTextureSize,
295+ maxViewportDims : `${ maxViewportDims [ 0 ] } x${ maxViewportDims [ 1 ] } ` ,
296+ shaderPrecision,
297+ extensionsCount,
298+ connectionType,
220299 degraded : s . degraded || isDegraded ,
300+ degradationEvents : degradationEventsRef . current ,
301+ totalFrames : totalFramesRef . current ,
302+ uptime : formatUptime ( ) ,
303+ reducedMotion : prefersReducedMotion ,
221304 targetFps : fpsRef . current === 0 ? "Uncapped (Native)" : fpsRef . current . toString ( )
222305 } ) ) ;
223- }
306+ } ;
224307
225308 const resize = ( ) => {
226309 const width = window . innerWidth ;
@@ -265,18 +348,30 @@ export function WebGLShader() {
265348
266349 // Track actual FPS
267350 framesRenderedRef . current ++ ;
351+ totalFramesRef . current ++ ;
268352 if ( currentTime - lastFpsCalculateTimeRef . current >= 1000 ) {
269353 const actualFps = framesRenderedRef . current ;
270- setStats ( s => ( { ...s , fps : actualFps , frameTimeMs : Number ( ( 1000 / Math . max ( actualFps , 1 ) ) . toFixed ( 2 ) ) } ) ) ;
354+ if ( actualFps > peakFpsRef . current ) peakFpsRef . current = actualFps ;
355+ if ( actualFps < minFpsRef . current && actualFps > 0 ) minFpsRef . current = actualFps ;
356+ setStats ( s => ( {
357+ ...s ,
358+ fps : actualFps ,
359+ peakFps : peakFpsRef . current ,
360+ minFps : minFpsRef . current === Infinity ? 0 : minFpsRef . current ,
361+ frameTimeMs : Number ( ( 1000 / Math . max ( actualFps , 1 ) ) . toFixed ( 2 ) ) ,
362+ totalFrames : totalFramesRef . current ,
363+ uptime : formatUptime ( ) ,
364+ degradationEvents : degradationEventsRef . current ,
365+ } ) ) ;
271366 framesRenderedRef . current = 0 ;
272367 lastFpsCalculateTimeRef . current = currentTime ;
273368
274369 // Frame degradation logic
275- // If native framerate drops terribly, seamlessly reduce resolution scaling
276370 if ( fpsRef . current === 0 && actualFps < 45 && actualFps > 0 ) {
277371 frameDropsRef . current ++ ;
278372 if ( frameDropsRef . current > 3 && currentPixelRatio > 0.5 ) {
279373 currentPixelRatio = Math . max ( 0.5 , currentPixelRatio - 0.5 ) ;
374+ degradationEventsRef . current ++ ;
280375 resize ( ) ;
281376 updateStats ( canvas . width , canvas . height , window . innerWidth , window . innerHeight , true ) ;
282377 frameDropsRef . current = 0 ;
@@ -340,32 +435,111 @@ export function WebGLShader() {
340435 { /* Stats for nerds toggle & panel */ }
341436 < div className = "fixed bottom-4 left-4 z-50 flex flex-col items-start gap-2 max-w-[calc(100vw-2rem)] pointer-events-none" >
342437 { showStats && (
343- < div className = "bg-black/80 backdrop-blur-md rounded-xl p-3 sm:p-4 text-[10px] sm:text-xs font-mono text-green-400 border border-green-500/30 w-[85vw] sm:w-80 shadow-2xl animate-fade-in shadow-black/50 overflow-hidden pointer-events-auto" >
344- < div className = "flex justify-between items-center mb-2 pb-2 border-b border-green-500/20" >
438+ < div className = "bg-black/80 backdrop-blur-md rounded-xl p-3 sm:p-4 text-[10px] sm:text-xs font-mono text-green-400 border border-green-500/30 w-[85vw] sm:w-80 shadow-2xl animate-fade-in shadow-black/50 pointer-events-auto max-h-[75vh] overflow-y -auto" >
439+ < div className = "flex justify-between items-center mb-2 pb-2 border-b border-green-500/20 sticky top-0 bg-black/80 backdrop-blur-md -mx-3 sm:-mx-4 px-3 sm:px-4 -mt-3 sm:-mt-4 pt-3 sm:pt-4 z-10 " >
345440 < span className = "font-bold text-green-300 tracking-wider" > STATS FOR NERDS</ span >
346441 < button onClick = { ( ) => setShowStats ( false ) } className = "text-green-500 hover:text-green-300 transition-colors p-1 -mr-1" >
347442 < X className = "w-4 h-4" />
348443 </ button >
349444 </ div >
350- < div className = "space-y-1" >
445+
446+ { /* Performance Section */ }
447+ < div className = "space-y-1 mb-2" >
448+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > Performance</ div >
351449 < div className = "flex justify-between" > < span > Current FPS:</ span > < span className = "text-white" > { stats . fps } < span className = "text-green-500/50" > ({ stats . frameTimeMs } ms)</ span > </ span > </ div >
450+ < div className = "flex justify-between" > < span > Peak FPS:</ span > < span className = "text-white" > { stats . peakFps } </ span > </ div >
451+ < div className = "flex justify-between" > < span > Min FPS:</ span > < span className = "text-white" > { stats . minFps } </ span > </ div >
352452 < div className = "flex justify-between" > < span > Target FPS:</ span > < span className = "text-white" > { stats . targetFps } </ span > </ div >
453+ < div className = "flex justify-between" > < span > Total Frames:</ span > < span className = "text-white" > { stats . totalFrames . toLocaleString ( ) } </ span > </ div >
454+ < div className = "flex justify-between" > < span > Uptime:</ span > < span className = "text-white" > { stats . uptime } </ span > </ div >
455+ </ div >
456+
457+ { /* Degradation Section */ }
458+ < div className = "space-y-1 mb-2 pt-2 border-t border-green-500/20" >
459+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > Health</ div >
353460 < div className = "flex justify-between" > < span > Degraded Mode:</ span > < span className = { stats . degraded ? "text-red-400 font-bold" : "text-white" } > { stats . degraded ? "CRITICAL" : "OK" } </ span > </ div >
461+ < div className = "flex justify-between" > < span > Degradation Events:</ span > < span className = { stats . degradationEvents > 0 ? "text-yellow-400" : "text-white" } > { stats . degradationEvents } </ span > </ div >
462+ < div className = "flex justify-between" > < span > Reduced Motion:</ span > < span className = "text-white" > { stats . reducedMotion ? "ON" : "OFF" } </ span > </ div >
463+ </ div >
354464
355- < div className = "flex justify-between mt-2 pt-2 border-t border-green-500/20" > < span > DPR Multiplier:</ span > < span className = "text-white" > { stats . dprMultiplier } x</ span > </ div >
465+ { /* Rendering Section */ }
466+ < div className = "space-y-1 mb-2 pt-2 border-t border-green-500/20" >
467+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > Rendering</ div >
468+ < div className = "flex justify-between" > < span > Native DPR:</ span > < span className = "text-white" > { stats . nativeDpr . toFixed ( 2 ) } x</ span > </ div >
469+ < div className = "flex justify-between" > < span > DPR Multiplier:</ span > < span className = "text-white" > { stats . dprMultiplier } x</ span > </ div >
356470 < div className = "flex justify-between" > < span > Actual DPR:</ span > < span className = "text-white" > { stats . actualDpr . toFixed ( 2 ) } x</ span > </ div >
357-
358- < div className = "flex justify-between mt-2 pt-2 border-t border-green-500/20" > < span > Logical Res:</ span > < span className = "text-white" > { stats . logicalWidth } x{ stats . logicalHeight } </ span > </ div >
471+ < div className = "flex justify-between" > < span > Logical Res:</ span > < span className = "text-white" > { stats . logicalWidth } x{ stats . logicalHeight } </ span > </ div >
359472 < div className = "flex justify-between" > < span > Render Res:</ span > < span className = "text-white" > { Math . round ( stats . canvasWidth ) } x{ Math . round ( stats . canvasHeight ) } </ span > </ div >
360- < div className = "flex justify-between text-[9px] text-green-500/60 " > < span className = "mr-4 sm:mr-8 whitespace-nowrap" > Pixels Processed:</ span > < span className = "text-right" > ~{ ( stats . canvasWidth * stats . canvasHeight ) . toLocaleString ( ) } px</ span > </ div >
473+ < div className = "flex justify-between text-[9px] text-green-500/60" > < span className = "mr-4 whitespace-nowrap" > Pixels/Frame:</ span > < span className = "text-right" > ~{ ( stats . canvasWidth * stats . canvasHeight ) . toLocaleString ( ) } px</ span > </ div >
474+ < div className = "flex justify-between" > < span > Color Depth:</ span > < span className = "text-white" > { stats . colorDepth } -bit</ span > </ div >
475+ </ div >
361476
362- < div className = "flex justify-between mt-2 pt-2 border-t border-green-500/20" > < span > CPU Threads:</ span > < span className = "text-white" > { stats . concurrency } </ span > </ div >
477+ { /* Hardware Section */ }
478+ < div className = "space-y-1 mb-2 pt-2 border-t border-green-500/20" >
479+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > Hardware</ div >
480+ < div className = "flex justify-between" > < span > CPU Threads:</ span > < span className = "text-white" > { stats . concurrency } </ span > </ div >
363481 < div className = "flex justify-between" > < span > Device RAM:</ span > < span className = "text-white" > ~{ stats . memory } GB</ span > </ div >
364- < div className = "flex flex-col mt-2 pt-2 border-t border-green-500/20" >
365- < span className = "mb-0.5" > GPU Subsystem:</ span >
482+ < div className = "flex justify-between" > < span > Platform:</ span > < span className = "text-white" > { stats . platform } </ span > </ div >
483+ < div className = "flex justify-between" > < span > Touch Points:</ span > < span className = "text-white" > { stats . touchPoints } </ span > </ div >
484+ < div className = "flex flex-col mt-1" >
485+ < span className = "mb-0.5" > GPU Vendor:</ span >
486+ < span className = "text-white opacity-80 leading-tight break-words text-[9px] sm:text-[10px]" > { stats . vendor } </ span >
487+ </ div >
488+ < div className = "flex flex-col mt-1" >
489+ < span className = "mb-0.5" > GPU Renderer:</ span >
366490 < span className = "text-white opacity-80 leading-tight break-words text-[9px] sm:text-[10px]" title = { stats . renderer } > { stats . renderer } </ span >
367491 </ div >
368492 </ div >
493+
494+ { /* WebGL Section */ }
495+ < div className = "space-y-1 mb-2 pt-2 border-t border-green-500/20" >
496+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > WebGL Context</ div >
497+ < div className = "flex justify-between" > < span > Version:</ span > < span className = "text-white" > { stats . webglVersion } </ span > </ div >
498+ < div className = "flex justify-between" > < span > Max Tex Size:</ span > < span className = "text-white" > { stats . maxTextureSize . toLocaleString ( ) } px</ span > </ div >
499+ < div className = "flex justify-between" > < span > Max Viewport:</ span > < span className = "text-white" > { stats . maxViewportDims } </ span > </ div >
500+ < div className = "flex justify-between" > < span > Shader Precision:</ span > < span className = "text-white" > { stats . shaderPrecision } </ span > </ div >
501+ < div className = "flex justify-between" > < span > Extensions:</ span > < span className = "text-white" > { stats . extensionsCount } </ span > </ div >
502+ </ div >
503+
504+ { /* Network Section */ }
505+ < div className = "space-y-1 mb-2 pt-2 border-t border-green-500/20" >
506+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > Network</ div >
507+ < div className = "flex justify-between" > < span > Connection:</ span > < span className = "text-white" > { stats . connectionType } </ span > </ div >
508+ </ div >
509+
510+ { /* Build & Source Section */ }
511+ < div className = "space-y-1 pt-2 border-t border-green-500/20" >
512+ < div className = "text-green-300/60 uppercase tracking-widest text-[8px] sm:text-[9px] mb-1" > Build & Source </ div >
513+ < div className = "flex justify-between" > < span > Branch:</ span > < span className = "text-white" > { process . env . NEXT_PUBLIC_GIT_BRANCH || "unknown" } </ span > </ div >
514+ < div className = "flex justify-between items-center" >
515+ < span > Commit:</ span >
516+ < a
517+ href = { `${ process . env . NEXT_PUBLIC_REPO_URL || "#" } /commit/${ process . env . NEXT_PUBLIC_GIT_COMMIT_HASH || "" } ` }
518+ target = "_blank"
519+ rel = "noopener noreferrer"
520+ className = "text-cyan-400 hover:text-cyan-300 underline underline-offset-2 transition-colors"
521+ >
522+ { process . env . NEXT_PUBLIC_GIT_COMMIT_SHORT || "unknown" }
523+ </ a >
524+ </ div >
525+ < div className = "flex flex-col mt-1" >
526+ < span className = "mb-0.5" > Message:</ span >
527+ < span className = "text-white opacity-80 leading-tight break-words text-[9px] sm:text-[10px]" > { process . env . NEXT_PUBLIC_GIT_COMMIT_MESSAGE || "N/A" } </ span >
528+ </ div >
529+ < div className = "flex justify-between" > < span > Commit Date:</ span > < span className = "text-white text-[9px]" > { process . env . NEXT_PUBLIC_GIT_COMMIT_DATE || "N/A" } </ span > </ div >
530+ < div className = "flex justify-between" > < span > Build Time:</ span > < span className = "text-white text-[9px]" > { process . env . NEXT_PUBLIC_BUILD_TIME || "N/A" } </ span > </ div >
531+ < div className = "flex justify-between items-center mt-1" >
532+ < span > Source Code:</ span >
533+ < a
534+ href = { process . env . NEXT_PUBLIC_REPO_URL || "#" }
535+ target = "_blank"
536+ rel = "noopener noreferrer"
537+ className = "text-cyan-400 hover:text-cyan-300 underline underline-offset-2 transition-colors"
538+ >
539+ GitHub ↗
540+ </ a >
541+ </ div >
542+ </ div >
369543 </ div >
370544 ) }
371545
0 commit comments