Skip to content

Commit 41f80d3

Browse files
committed
feat(webgl): Uncapped FPS native rendering with stats for nerds pane and graceful degradation
1 parent 9bc528f commit 41f80d3

2 files changed

Lines changed: 142 additions & 46 deletions

File tree

agents.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ Quick reference for “who to ping for what”.
3434
|-----------|---------------------------|---------------------------------------------------------|
3535
| **Yash** | Founder & Local Lead | Leadership, Event Management, Team Coordination, Strategic Planning |
3636
| **Aadrika** | Co-Founder & Chief Creative Strategist | Creative Strategy, Brand Development, Campaign Planning, Design Direction |
37-
| **Akshat Kushwaha** | Co-Founder & Technical Lead | AI & LLMOps, Cloud Infrastructure, Full-Stack Development |
38-
| **Devansh** | Founding Member & Backend Lead | Backend Development, Database Architecture, Community Outreach, Partnership Building |
37+
| **Akshat Kushwaha** | Co-Founder & Technical Lead | Full-Stack (Frontend & Backend), Auth, Databases, AI/LLMOps, Agents |
38+
| **Devansh** | Founding Member & Backend Co-Lead | Backend Development, Database Architecture, Community Outreach, Partnership Building |
3939
| **Maryam** | Social Media & Promotions Head | Social Media, Brand Identity, Visual Communication, Design Systems |
4040
| **Srishti** | Operations & Communications Head | Operations Management, Team Communications, Project Coordination, Process Optimization |
4141

@@ -68,24 +68,24 @@ Quick reference for “who to ping for what”.
6868
- You want to align a project with the overall brand strategy
6969

7070
#### Akshat Kushwaha – Co-Founder & Technical Lead
71-
- **Expertise:** AI & LLMOps, Cloud Infrastructure, Full-Stack Development
71+
- **Expertise:** AI & LLMOps, Cloud Infrastructure, Full-Stack Development (Frontend & Backend), Authentication, Databases, Agents
7272
- **Owns:**
73-
- Building and maintaining the website
74-
- Leading programming projects and evaluating tech stacks
75-
- Technical stability of all projects
73+
- Building and maintaining the website's frontend and backend architectures
74+
- Leading programming projects, evaluating tech stacks, and managing databases/auth
75+
- Technical stability of all projects and developing AI agents
7676
- **Talk to Akshat when…**
77-
- You need to propose or evaluate a technical stack
78-
- You need help with agent development or cloud infrastructure
79-
- You want to implement AI integrations or backend systems
77+
- You need to propose or evaluate a technical stack, database design, or auth flow
78+
- You need help with agent development, full-stack systems, or cloud infrastructure
79+
- You want to implement AI integrations or backend/frontend systems
8080

8181
---
8282

8383
### 3.2 Engineering & Development
8484

85-
#### Devansh – Founding Member & Backend Lead
85+
#### Devansh – Founding Member & Backend Co-Lead
8686
- **Expertise:** Backend Development, Database Architecture, Community Outreach, Partnership Building
8787
- **Owns:**
88-
- Backend development and technical features
88+
- Co-leading backend development and technical features
8989
- Building relationships with schools and students
9090
- Engaging with external communities for participation
9191
- **Talk to Devansh when…**

components/ui/web-gl-shader.tsx

Lines changed: 131 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
"use client";
22

33
import { useEffect, useRef, useState } from "react";
4+
import { Terminal, X } from "lucide-react";
45

56
export function WebGLShader() {
67
const canvasRef = useRef<HTMLCanvasElement>(null);
78
const animationRef = useRef<number | null>(null);
89
const lastFrameTimeRef = useRef<number>(0);
10+
const framesRenderedRef = useRef<number>(0);
11+
const lastFpsCalculateTimeRef = useRef<number>(0);
12+
913
const [isVisible, setIsVisible] = useState(true);
1014
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
1115
const [forceStaticFallback, setForceStaticFallback] = useState(false);
12-
const fpsRef = useRef<number>(60);
16+
const [showStats, setShowStats] = useState(false);
17+
18+
// Stats state
19+
const [stats, setStats] = useState({
20+
fps: 0,
21+
targetFps: "Uncapped (Native)",
22+
dprMultiplier: 1.5,
23+
actualDpr: 1,
24+
canvasWidth: 0,
25+
canvasHeight: 0,
26+
concurrency: 4,
27+
memory: 4,
28+
renderer: "Unknown",
29+
vendor: "Unknown",
30+
degraded: false,
31+
});
32+
33+
const fpsRef = useRef<number>(0); // 0 means uncapped
1334
const frameDropsRef = useRef<number>(0);
1435

1536
useEffect(() => {
@@ -47,18 +68,20 @@ export function WebGLShader() {
4768

4869
// --- Hardware Feature Detection ---
4970
let dprMultiplier = 1.5;
71+
let concurrency = 4;
72+
let memory = 4;
73+
5074
if (typeof navigator !== "undefined") {
51-
const concurrency = navigator.hardwareConcurrency || 4;
75+
concurrency = navigator.hardwareConcurrency || 4;
5276
// @ts-expect-error deviceMemory is non-standard but widely supported in Chromium
53-
const memory = navigator.deviceMemory || 4;
77+
memory = navigator.deviceMemory || 4;
5478

5579
if (concurrency <= 2 || memory <= 2) {
5680
// Extremely low end device, skip WebGL entirely to save battery and avoid panics
5781
setForceStaticFallback(true);
5882
return;
5983
} else if (concurrency <= 4 || memory <= 4) {
6084
// Lower end device, start with conservative defaults
61-
fpsRef.current = 30;
6285
dprMultiplier = 1.0;
6386
}
6487
}
@@ -79,7 +102,11 @@ export function WebGLShader() {
79102
return;
80103
}
81104

82-
const pixelRatio = Math.min(window.devicePixelRatio, dprMultiplier);
105+
const rendererDebugInfo = gl.getExtension("WEBGL_debug_renderer_info");
106+
const renderer = rendererDebugInfo ? gl.getParameter(rendererDebugInfo.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
107+
const vendor = rendererDebugInfo ? gl.getParameter(rendererDebugInfo.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR);
108+
109+
let currentPixelRatio = Math.min(window.devicePixelRatio, dprMultiplier);
83110

84111
const vertexShaderSource = `
85112
attribute vec2 position;
@@ -173,18 +200,38 @@ export function WebGLShader() {
173200

174201
let time = 0;
175202

203+
// Stats update helper
204+
const updateStats = (w: number, h: number, isDegraded = false) => {
205+
setStats(s => ({
206+
...s,
207+
canvasWidth: w,
208+
canvasHeight: h,
209+
concurrency,
210+
memory,
211+
renderer,
212+
vendor,
213+
dprMultiplier,
214+
actualDpr: currentPixelRatio,
215+
degraded: s.degraded || isDegraded,
216+
targetFps: fpsRef.current === 0 ? "Uncapped (Native)" : fpsRef.current.toString()
217+
}));
218+
}
219+
176220
const resize = () => {
177221
const width = window.innerWidth;
178222
const height = window.innerHeight;
179-
canvas.width = width * pixelRatio;
180-
canvas.height = height * pixelRatio;
223+
canvas.width = width * currentPixelRatio;
224+
canvas.height = height * currentPixelRatio;
181225
canvas.style.width = `${width}px`;
182226
canvas.style.height = `${height}px`;
183227
gl.viewport(0, 0, canvas.width, canvas.height);
184228
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
229+
230+
updateStats(canvas.width, canvas.height);
185231
};
186232

187233
resize();
234+
lastFpsCalculateTimeRef.current = performance.now();
188235

189236
// Debounced resize handler
190237
let resizeTimeout: NodeJS.Timeout;
@@ -198,31 +245,42 @@ export function WebGLShader() {
198245
const animate = (currentTime: number) => {
199246
animationRef.current = requestAnimationFrame(animate);
200247

201-
// Skip if not visible or reduced motion is preferred
202248
if (!isVisible || prefersReducedMotion) return;
203249

204-
// Frame rate limiting
205250
const elapsed = currentTime - lastFrameTimeRef.current;
206-
const currentFrameInterval = 1000 / fpsRef.current;
207-
208-
if (elapsed < currentFrameInterval) return;
209-
210-
// Dynamic Framerate Scaling (monitor requested framerate vs actual)
211-
// Ignore huge jumps (like switching tabs) which might spike the elapsed time
212-
if (elapsed > currentFrameInterval * 1.5 && elapsed < 1000) {
213-
frameDropsRef.current++;
214-
// If we consistently drop frames (e.g. 30 frames dropped), lower the target FPS
215-
if (frameDropsRef.current > 30 && fpsRef.current > 30) {
216-
fpsRef.current = 30;
217-
frameDropsRef.current = 0;
218-
console.warn("WebGL Shader: Performance degraded, dynamically scaling target FPS down to 30.");
219-
}
251+
252+
// If we have a target FPS (e.g. from downscaling), throttle it
253+
if (fpsRef.current > 0) {
254+
const currentFrameInterval = 1000 / fpsRef.current;
255+
if (elapsed < currentFrameInterval) return;
256+
lastFrameTimeRef.current = currentTime - (elapsed % currentFrameInterval);
220257
} else {
221-
// Recover frame drop count if it's hitting targets consistently
222-
frameDropsRef.current = Math.max(0, frameDropsRef.current - 1);
258+
lastFrameTimeRef.current = currentTime;
223259
}
224260

225-
lastFrameTimeRef.current = currentTime - (elapsed % currentFrameInterval);
261+
// Track actual FPS
262+
framesRenderedRef.current++;
263+
if (currentTime - lastFpsCalculateTimeRef.current >= 1000) {
264+
const actualFps = framesRenderedRef.current;
265+
setStats(s => ({ ...s, fps: actualFps }));
266+
framesRenderedRef.current = 0;
267+
lastFpsCalculateTimeRef.current = currentTime;
268+
269+
// Frame degradation logic
270+
// If native framerate drops terribly, seamlessly reduce resolution scaling
271+
if (fpsRef.current === 0 && actualFps < 45 && actualFps > 0) {
272+
frameDropsRef.current++;
273+
if (frameDropsRef.current > 3 && currentPixelRatio > 0.5) {
274+
currentPixelRatio = Math.max(0.5, currentPixelRatio - 0.5);
275+
resize();
276+
updateStats(canvas.width, canvas.height, true);
277+
frameDropsRef.current = 0;
278+
console.warn("WebGL Shader: Performance degraded, dropping pixel multiplier to", currentPixelRatio);
279+
}
280+
} else if (actualFps >= 50) {
281+
frameDropsRef.current = Math.max(0, frameDropsRef.current - 1);
282+
}
283+
}
226284

227285
// Slower time progression
228286
time += 0.01;
@@ -263,14 +321,52 @@ export function WebGLShader() {
263321
}
264322

265323
return (
266-
<canvas
267-
ref={canvasRef}
268-
className="fixed top-0 left-0 z-0 pointer-events-none"
269-
style={{
270-
width: "100vw",
271-
height: "100vh",
272-
willChange: "auto",
273-
}}
274-
/>
324+
<>
325+
<canvas
326+
ref={canvasRef}
327+
className="fixed top-0 left-0 z-0 pointer-events-none"
328+
style={{
329+
width: "100vw",
330+
height: "100vh",
331+
willChange: "auto",
332+
}}
333+
/>
334+
335+
{/* Stats for nerds toggle & panel */}
336+
<div className="fixed bottom-4 left-4 z-50 flex flex-col items-start gap-2">
337+
{showStats && (
338+
<div className="bg-black/80 backdrop-blur-md rounded-xl p-4 text-xs font-mono text-green-400 border border-green-500/30 w-72 shadow-2xl animate-fade-in shadow-black/50">
339+
<div className="flex justify-between items-center mb-2 pb-2 border-b border-green-500/20">
340+
<span className="font-bold text-green-300 tracking-wider">STATS FOR NERDS</span>
341+
<button onClick={() => setShowStats(false)} className="text-green-500 hover:text-green-300 transition-colors">
342+
<X className="w-4 h-4" />
343+
</button>
344+
</div>
345+
<div className="space-y-1">
346+
<div className="flex justify-between"><span>Current FPS:</span><span className="text-white">{stats.fps}</span></div>
347+
<div className="flex justify-between"><span>Target FPS:</span><span className="text-white">{stats.targetFps}</span></div>
348+
<div className="flex justify-between"><span>Degraded Mode:</span><span className={stats.degraded ? "text-red-400 font-bold" : "text-white"}>{stats.degraded ? "YES" : "NO"}</span></div>
349+
<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>
350+
<div className="flex justify-between"><span>Actual DPR:</span><span className="text-white">{stats.actualDpr.toFixed(2)}x</span></div>
351+
<div className="flex justify-between mt-2 pt-2 border-t border-green-500/20"><span>Resolution:</span><span className="text-white">{stats.canvasWidth} x {stats.canvasHeight}</span></div>
352+
<div className="flex justify-between mt-2 pt-2 border-t border-green-500/20"><span>Concurrency:</span><span className="text-white">{stats.concurrency} Cores</span></div>
353+
<div className="flex justify-between"><span>Device RAM:</span><span className="text-white">~{stats.memory} GB</span></div>
354+
<div className="flex flex-col mt-2 pt-2 border-t border-green-500/20">
355+
<span>GPU Renderer:</span>
356+
<span className="text-white truncate opacity-80 mt-0.5" title={stats.renderer}>{stats.renderer}</span>
357+
</div>
358+
</div>
359+
</div>
360+
)}
361+
362+
<button
363+
onClick={() => setShowStats(!showStats)}
364+
className="group p-2.5 rounded-full bg-black/40 backdrop-blur-md border border-white/10 hover:bg-black/60 transition-all text-white/50 hover:text-white"
365+
title="Stats for nerds"
366+
>
367+
<Terminal className="w-4 h-4" />
368+
</button>
369+
</div>
370+
</>
275371
);
276372
}

0 commit comments

Comments
 (0)