Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Ultimate FPS Benchmark</title> | |
| <!-- Fonts --> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet"> | |
| <!-- Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #00f3ff; | |
| --secondary: #bc13fe; | |
| --accent: #ffee00; | |
| --bg-dark: #050507; | |
| --panel-bg: rgba(10, 10, 14, 0.75); | |
| --border: rgba(255, 255, 255, 0.1); | |
| --text-main: #e0e0e0; | |
| --font-display: 'Orbitron', sans-serif; | |
| --font-mono: 'JetBrains Mono', monospace; | |
| --glass: blur(12px) saturate(180%); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| user-select: none; | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| font-family: var(--font-mono); | |
| overflow: hidden; /* Canvas covers everything */ | |
| height: 100vh; | |
| width: 100vw; | |
| } | |
| /* --- Header --- */ | |
| header { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 1rem 2rem; | |
| z-index: 100; | |
| pointer-events: none; /* Let clicks pass through to canvas where needed */ | |
| } | |
| .brand { | |
| font-family: var(--font-display); | |
| font-size: 1.5rem; | |
| font-weight: 900; | |
| letter-spacing: 2px; | |
| color: white; | |
| text-shadow: 0 0 10px var(--primary); | |
| pointer-events: auto; | |
| } | |
| .brand span { | |
| color: var(--primary); | |
| } | |
| .anycoder-link { | |
| font-family: var(--font-mono); | |
| font-size: 0.8rem; | |
| color: var(--text-main); | |
| text-decoration: none; | |
| background: rgba(255, 255, 255, 0.05); | |
| padding: 8px 16px; | |
| border-radius: 4px; | |
| border: 1px solid var(--border); | |
| transition: all 0.3s ease; | |
| pointer-events: auto; | |
| backdrop-filter: blur(5px); | |
| } | |
| .anycoder-link:hover { | |
| background: rgba(0, 243, 255, 0.15); | |
| border-color: var(--primary); | |
| box-shadow: 0 0 15px rgba(0, 243, 255, 0.3); | |
| color: white; | |
| } | |
| /* --- Main Canvas --- */ | |
| #renderCanvas { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 1; | |
| } | |
| /* --- HUD / Dashboard --- */ | |
| .hud-container { | |
| position: absolute; | |
| top: 80px; | |
| left: 2rem; | |
| width: 320px; | |
| z-index: 10; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| pointer-events: none; | |
| } | |
| .panel { | |
| background: var(--panel-bg); | |
| backdrop-filter: var(--glass); | |
| -webkit-backdrop-filter: var(--glass); | |
| border: 1px solid var(--border); | |
| border-left: 4px solid var(--primary); | |
| padding: 1.5rem; | |
| border-radius: 4px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); | |
| pointer-events: auto; | |
| transition: transform 0.2s; | |
| } | |
| .panel:hover { | |
| border-color: rgba(255,255,255,0.2); | |
| } | |
| .fps-display { | |
| text-align: center; | |
| } | |
| .fps-value { | |
| font-family: var(--font-display); | |
| font-size: 4rem; | |
| font-weight: 900; | |
| line-height: 1; | |
| color: white; | |
| text-shadow: 0 0 20px rgba(0, 243, 255, 0.5); | |
| } | |
| .fps-label { | |
| color: var(--primary); | |
| font-size: 0.9rem; | |
| text-transform: uppercase; | |
| letter-spacing: 4px; | |
| margin-bottom: 10px; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-top: 1rem; | |
| border-top: 1px solid var(--border); | |
| padding-top: 1rem; | |
| } | |
| .stat-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| .stat-item span:first-child { | |
| font-size: 0.7rem; | |
| color: #888; | |
| text-transform: uppercase; | |
| } | |
| .stat-item span:last-child { | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| color: white; | |
| } | |
| /* --- Graph Panel --- */ | |
| .graph-panel { | |
| height: 150px; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 1rem; | |
| } | |
| .graph-title { | |
| font-size: 0.8rem; | |
| color: #aaa; | |
| margin-bottom: 0.5rem; | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| canvas#fpsGraph { | |
| width: 100%; | |
| height: 100%; | |
| border-bottom: 1px solid var(--border); | |
| border-left: 1px solid var(--border); | |
| } | |
| /* --- Controls --- */ | |
| .controls-container { | |
| position: absolute; | |
| bottom: 2rem; | |
| left: 2rem; | |
| right: 2rem; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-end; | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| .control-panel { | |
| background: var(--panel-bg); | |
| backdrop-filter: var(--glass); | |
| -webkit-backdrop-filter: var(--glass); | |
| border: 1px solid var(--border); | |
| border-top: 4px solid var(--secondary); | |
| padding: 1.5rem 2rem; | |
| border-radius: 12px; | |
| pointer-events: auto; | |
| width: 100%; | |
| max-width: 800px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| } | |
| .control-row { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 2rem; | |
| flex-wrap: wrap; | |
| } | |
| .slider-group { | |
| flex: 1; | |
| min-width: 200px; | |
| } | |
| .slider-label { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 0.5rem; | |
| font-size: 0.9rem; | |
| color: var(--secondary); | |
| font-weight: 700; | |
| } | |
| input[type="range"] { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 6px; | |
| background: rgba(255,255,255,0.1); | |
| border-radius: 3px; | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: var(--secondary); | |
| cursor: pointer; | |
| box-shadow: 0 0 10px var(--secondary); | |
| transition: transform 0.2s; | |
| } | |
| input[type="range"]::-webkit-slider-thumb:hover { | |
| transform: scale(1.2); | |
| } | |
| .btn-group { | |
| display: flex; | |
| gap: 1rem; | |
| } | |
| .btn { | |
| background: transparent; | |
| border: 1px solid var(--border); | |
| color: white; | |
| padding: 10px 20px; | |
| border-radius: 4px; | |
| font-family: var(--font-mono); | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| text-transform: uppercase; | |
| font-size: 0.85rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .btn:hover { | |
| background: rgba(255,255,255,0.05); | |
| border-color: white; | |
| } | |
| .btn-primary { | |
| background: var(--secondary); | |
| border-color: var(--secondary); | |
| color: black; | |
| font-weight: bold; | |
| } | |
| .btn-primary:hover { | |
| background: #d45aff; | |
| box-shadow: 0 0 15px rgba(188, 19, 254, 0.4); | |
| } | |
| /* --- Responsive --- */ | |
| @media (max-width: 768px) { | |
| header { | |
| padding: 1rem; | |
| } | |
| .hud-container { | |
| left: 1rem; | |
| right: 1rem; | |
| width: auto; | |
| top: 70px; | |
| } | |
| .fps-value { | |
| font-size: 3rem; | |
| } | |
| .controls-container { | |
| bottom: 1rem; | |
| left: 1rem; | |
| right: 1rem; | |
| } | |
| .control-panel { | |
| padding: 1rem; | |
| } | |
| .control-row { | |
| flex-direction: column; | |
| align-items: stretch; | |
| gap: 1rem; | |
| } | |
| .btn-group { | |
| justify-content: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-bolt"></i> FPS<span>TESTER</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <canvas id="renderCanvas"></canvas> | |
| <div class="hud-container"> | |
| <!-- FPS Panel --> | |
| <div class="panel"> | |
| <div class="fps-display"> | |
| <div class="fps-label">Current FPS</div> | |
| <div class="fps-value" id="fpsDisplay">00</div> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-item"> | |
| <span>Avg</span> | |
| <span id="avgFps">0</span> | |
| </div> | |
| <div class="stat-item"> | |
| <span>Min 1%</span> | |
| <span id="minFps">0</span> | |
| </div> | |
| <div class="stat-item"> | |
| <span>Objects</span> | |
| <span id="objCount">0</span> | |
| </div> | |
| <div class="stat-item"> | |
| <span>Res</span> | |
| <span id="resDisplay">1080p</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Graph Panel --> | |
| <div class="panel graph-panel"> | |
| <div class="graph-title"> | |
| <span>FPS HISTORY</span> | |
| <span style="color:var(--primary)">60s</span> | |
| </div> | |
| <canvas id="fpsGraph"></canvas> | |
| </div> | |
| </div> | |
| <div class="controls-container"> | |
| <div class="control-panel"> | |
| <div class="control-row"> | |
| <div class="slider-group"> | |
| <div class="slider-label"> | |
| <span><i class="fa-solid fa-cubes"></i> STRESS LEVEL (Particles)</span> | |
| <span id="sliderValue">1000</span> | |
| </div> | |
| <input type="range" id="particleSlider" min="100" max="20000" step="100" value="1000"> | |
| </div> | |
| </div> | |
| <div class="control-row"> | |
| <div class="btn-group"> | |
| <button class="btn" id="togglePhysics"><i class="fa-solid fa-magnet"></i> Physics</button> | |
| <button class="btn" id="toggleTrails"><i class="fa-solid fa-wind"></i> Trails</button> | |
| </div> | |
| <button class="btn btn-primary" id="resetBtn"><i class="fa-solid fa-rotate-right"></i> Reset Stats</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| /** | |
| * FPS TESTER APPLICATION | |
| * | |
| * Logic: | |
| * 1. Render Loop: Uses requestAnimationFrame. | |
| * 2. Particle System: Spawns objects to stress the GPU/CPU. | |
| * 3. Monitoring: Calculates frame delta time to determine FPS. | |
| */ | |
| class ParticleSystem { | |
| constructor(canvas) { | |
| this.canvas = canvas; | |
| this.ctx = canvas.getContext('2d', { alpha: false }); // Optimize for speed | |
| this.particles = []; | |
| this.physicsEnabled = true; | |
| this.trailsEnabled = false; | |
| this.colors = ['#00f3ff', '#bc13fe', '#ffee00', '#ffffff']; | |
| this.resize(); | |
| window.addEventListener('resize', () => this.resize()); | |
| } | |
| resize() { | |
| this.width = window.innerWidth; | |
| this.height = window.innerHeight; | |
| this.canvas.width = this.width; | |
| this.canvas.height = this.height; | |
| document.getElementById('resDisplay').innerText = `${this.width}x${this.height}`; | |
| } | |
| createParticle() { | |
| const size = Math.random() * 3 + 1; | |
| return { | |
| x: Math.random() * this.width, | |
| y: Math.random() * this.height, | |
| vx: (Math.random() - 0.5) * 4, | |
| vy: (Math.random() - 0.5) * 4, | |
| size: size, | |
| color: this.colors[Math.floor(Math.random() * this.colors.length)], | |
| history: [] | |
| }; | |
| } | |
| updateCount(count) { | |
| const currentLen = this.particles.length; | |
| if (count > currentLen) { | |
| for (let i = 0; i < count - currentLen; i++) { | |
| this.particles.push(this.createParticle()); | |
| } | |
| } else { | |
| this.particles.length = count; | |
| } | |
| document.getElementById('objCount').innerText = this.particles.length.toLocaleString(); | |
| } | |
| render() { | |
| // Clear Canvas (or create trail effect) | |
| if (this.trailsEnabled) { | |
| this.ctx.fillStyle = 'rgba(5, 5, 7, 0.2)'; // Trail fade | |
| this.ctx.fillRect(0, 0, this.width, this.height); | |
| } else { | |
| this.ctx.fillStyle = '#050507'; | |
| this.ctx.fillRect(0, 0, this.width, this.height); | |
| } | |
| this.ctx.lineWidth = 1; | |
| for (let i = 0; i < this.particles.length; i++) { | |
| let p = this.particles[i]; | |
| // Physics Update | |
| if (this.physicsEnabled) { | |
| p.x += p.vx; | |
| p.y += p.vy; | |
| // Boundary Check | |
| if (p.x < 0 || p.x > this.width) p.vx *= -1; | |
| if (p.y < 0 || p.y > this.height) p.vy *= -1; | |
| } | |
| // Render | |
| this.ctx.fillStyle = p.color; | |
| this.ctx.beginPath(); | |
| this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); | |
| this.ctx.fill(); | |
| } | |
| } | |
| } | |
| class FPSMonitor { | |
| constructor() { | |
| this.fpsDisplay = document.getElementById('fpsDisplay'); | |
| this.avgDisplay = document.getElementById('avgFps'); | |
| this.minDisplay = document.getElementById('minFps'); | |
| this.frames = 0; | |
| this.lastTime = performance.now(); | |
| this.fpsHistory = []; | |
| this.minHistory = []; | |
| // Graph setup | |
| this.graphCanvas = document.getElementById('fpsGraph'); | |
| this.graphCtx = this.graphCanvas.getContext('2d'); | |
| this.graphData = new Array(60).fill(0); // 60 data points | |
| this.resizeGraph(); | |
| window.addEventListener('resize', () => this.resizeGraph()); | |
| // Reset logic | |
| this.startTime = performance.now(); | |
| this.totalFrames = 0; | |
| } | |
| resizeGraph() { | |
| const rect = this.graphCanvas.parentElement.getBoundingClientRect(); | |
| this.graphCanvas.width = rect.width - 32; // padding adjustment | |
| this.graphCanvas.height = rect.height - 30; | |
| } | |
| reset() { | |
| this.fpsHistory = []; | |
| this.minHistory = []; | |
| this.totalFrames = 0; | |
| this.startTime = performance.now(); | |
| this.graphData.fill(0); | |
| this.updateUI(0, 0, 0); | |
| } | |
| update() { | |
| const now = performance.now(); | |
| this.frames++; | |
| this.totalFrames++; | |
| if (now >= this.lastTime + 1000) { | |
| const fps = Math.round((this.frames * 1000) / (now - this.lastTime)); | |
| this.fpsHistory.push(fps); | |
| if (this.fpsHistory.length > 60) this.fpsHistory.shift(); | |
| // Calculate Stats | |
| const avg = Math.round((this.totalFrames * 1000) / (now - this.startTime)); | |
| const min = Math.min(...this.fpsHistory); | |
| this.updateUI(fps, avg, min); | |
| this.updateGraph(fps); | |
| this.frames = 0; | |
| this.lastTime = now; | |
| } | |
| } | |
| updateUI(fps, avg, min) { | |
| this.fpsDisplay.innerText = fps; | |
| this.avgDisplay.innerText = avg; | |
| this.minDisplay.innerText = min; | |
| // Color coding | |
| if(fps >= 55) this.fpsDisplay.style.color = '#00f3ff'; // Good | |
| else if(fps >= 30) this.fpsDisplay.style.color = '#ffee00'; // Warning | |
| else this.fpsDisplay.style.color = '#ff0055'; // Bad | |
| } | |
| updateGraph(currentFps) { | |
| this.graphData.shift(); | |
| this.graphData.push(currentFps); | |
| const ctx = this.graphCtx; | |
| const w = this.graphCanvas.width; | |
| const h = this.graphCanvas.height; | |
| ctx.clearRect(0, 0, w, h); | |
| // Draw Guide Lines | |
| ctx.strokeStyle = '#333'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, h/2); | |
| ctx.lineTo(w, h/2); // 30 FPS line roughly | |
| ctx.stroke(); | |
| // Draw Graph | |
| ctx.strokeStyle = '#00f3ff'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| const step = w / (this.graphData.length - 1); | |
| const maxVal = 144; // Normalize to 144hz for visual scaling | |
| for (let i = 0; i < this.graphData.length; i++) { | |
| const val = this.graphData[i]; | |
| const y = h - (Math.min(val, maxVal) / maxVal) * h; | |
| if (i === 0) ctx.moveTo(0, y); | |
| else ctx.lineTo(i * step, y); | |
| } | |
| ctx.stroke(); | |
| // Fill gradient under | |
| ctx.lineTo(w, h); | |
| ctx.lineTo(0, h); | |
| ctx.closePath(); | |
| const grad = ctx.createLinearGradient(0, 0, 0, h); | |
| grad.addColorStop(0, 'rgba(0, 243, 255, 0.2)'); | |
| grad.addColorStop(1, 'rgba(0, 243, 255, 0)'); | |
| ctx.fillStyle = grad; | |
| ctx.fill(); | |
| } | |
| } | |
| // --- Init --- | |
| const canvas = document.getElementById('renderCanvas'); | |
| const particleSystem = new ParticleSystem(canvas); | |
| const fpsMonitor = new FPSMonitor(); | |
| // UI Inputs | |
| const slider = document.getElementById('particleSlider'); | |
| const sliderVal = document.getElementById('sliderValue'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const physicsBtn = document.getElementById('togglePhysics'); | |
| const trailsBtn = document.getElementById('toggleTrails'); | |
| // Initial Load | |
| particleSystem.updateCount(parseInt(slider.value)); | |
| // Event Listeners | |
| slider.addEventListener('input', (e) => { | |
| const val = parseInt(e.target.value); | |
| sliderVal.innerText = val.toLocaleString(); | |
| particleSystem.updateCount(val); | |
| }); | |
| resetBtn.addEventListener('click', () => { | |
| fpsMonitor.reset(); | |
| }); | |
| physicsBtn.addEventListener('click', (e) => { | |
| particleSystem.physicsEnabled = !particleSystem.physicsEnabled; | |
| e.currentTarget.classList.toggle('btn-primary'); | |
| e.currentTarget.style.borderColor = particleSystem.physicsEnabled ? 'var(--border)' : '#555'; | |
| e.currentTarget.style.opacity = particleSystem.physicsEnabled ? '1' : '0.5'; | |
| }); | |
| trailsBtn.addEventListener('click', (e) => { | |
| particleSystem.trailsEnabled = !particleSystem.trailsEnabled; | |
| e.currentTarget.classList.toggle('btn-primary'); | |
| }); | |
| // Main Loop | |
| function animate() { | |
| particleSystem.render(); | |
| fpsMonitor.update(); | |
| requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| </script> | |
| </body> | |
| </html> |