benchmark-mobile-gpu / index.html
foss22's picture
Add 2 files
e7169ff verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPU Benchmark for Mobile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #6200ee;
--primary-dark: #3700b3;
--secondary: #03dac6;
--background: #121212;
--surface: #1e1e1e;
--error: #cf6679;
--text-primary: rgba(255, 255, 255, 0.87);
--text-secondary: rgba(255, 255, 255, 0.6);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: var(--background);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
h1 {
font-size: 2rem;
margin-bottom: 10px;
background: linear-gradient(45deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.subtitle {
color: var(--text-secondary);
font-size: 0.9rem;
}
.card {
background-color: var(--surface);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.test-container {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 20px;
}
.test-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 8px;
transition: all 0.3s ease;
}
.test-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.test-info {
display: flex;
flex-direction: column;
}
.test-name {
font-weight: 500;
}
.test-description {
font-size: 0.8rem;
color: var(--text-secondary);
}
.test-result {
font-weight: 600;
}
.progress-container {
width: 100%;
height: 6px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 3px;
margin-top: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border-radius: 3px;
width: 0%;
transition: width 0.5s ease;
}
button {
background-color: var(--primary);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(98, 0, 238, 0.3);
}
button:hover {
background-color: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(98, 0, 238, 0.4);
}
button:disabled {
background-color: rgba(98, 0, 238, 0.5);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.result-container {
margin-top: 30px;
opacity: 0;
transition: opacity 0.5s ease;
}
.result-container.show {
opacity: 1;
}
.score {
font-size: 3rem;
font-weight: 700;
text-align: center;
margin: 20px 0;
background: linear-gradient(45deg, var(--secondary), var(--primary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.device-info {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
margin-top: 20px;
}
.info-item {
background-color: rgba(255, 255, 255, 0.05);
padding: 10px;
border-radius: 8px;
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.7rem;
color: var(--text-secondary);
margin-bottom: 5px;
}
.info-value {
font-size: 0.9rem;
font-weight: 500;
}
.canvas-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
opacity: 0.1;
}
canvas {
display: block;
}
.spinner {
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
display: none;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
footer {
margin-top: auto;
text-align: center;
padding: 20px;
color: var(--text-secondary);
font-size: 0.8rem;
}
.share-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.share-button {
flex: 1;
background-color: rgba(255, 255, 255, 0.1);
color: white;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.share-button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
@media (max-width: 480px) {
h1 {
font-size: 1.5rem;
}
.device-info {
grid-template-columns: 1fr 1fr;
}
}
</style>
</head>
<body>
<div class="canvas-container">
<canvas id="backgroundCanvas"></canvas>
</div>
<header>
<h1>Mobile GPU Benchmark</h1>
<p class="subtitle">Test your device's WebGL performance in the browser</p>
</header>
<main>
<div class="card">
<div class="test-container">
<div class="test-item">
<div class="test-info">
<span class="test-name">WebGL Support</span>
<span class="test-description">Checking WebGL availability</span>
<div class="progress-container">
<div class="progress-bar" id="webgl-progress"></div>
</div>
</div>
<span class="test-result" id="webgl-result">-</span>
</div>
<div class="test-item">
<div class="test-info">
<span class="test-name">Shader Complexity</span>
<span class="test-description">Testing fragment shader performance</span>
<div class="progress-container">
<div class="progress-bar" id="shader-progress"></div>
</div>
</div>
<span class="test-result" id="shader-result">-</span>
</div>
<div class="test-item">
<div class="test-info">
<span class="test-name">Texture Handling</span>
<span class="test-description">Testing texture rendering capabilities</span>
<div class="progress-container">
<div class="progress-bar" id="texture-progress"></div>
</div>
</div>
<span class="test-result" id="texture-result">-</span>
</div>
<div class="test-item">
<div class="test-info">
<span class="test-name">Geometry Processing</span>
<span class="test-description">Testing vertex processing speed</span>
<div class="progress-container">
<div class="progress-bar" id="geometry-progress"></div>
</div>
</div>
<span class="test-result" id="geometry-result">-</span>
</div>
<div class="test-item">
<div class="test-info">
<span class="test-name">Compute Performance</span>
<span class="test-description">Testing general computational power</span>
<div class="progress-container">
<div class="progress-bar" id="compute-progress"></div>
</div>
</div>
<span class="test-result" id="compute-result">-</span>
</div>
</div>
<button id="runBenchmark">
<span id="buttonText">Run Benchmark</span>
<div class="spinner" id="spinner"></div>
</button>
</div>
<div class="card result-container" id="resultContainer">
<h2>Benchmark Results</h2>
<div class="score" id="finalScore">0</div>
<div class="device-info">
<div class="info-item">
<span class="info-label">Device Performance</span>
<span class="info-value" id="performance-category">-</span>
</div>
<div class="info-item">
<span class="info-label">Renderer</span>
<span class="info-value" id="renderer-info">-</span>
</div>
<div class="info-item">
<span class="info-label">Resolution</span>
<span class="info-value" id="resolution-info">-</span>
</div>
<div class="info-item">
<span class="info-label">FPS</span>
<span class="info-value" id="fps-info">-</span>
</div>
</div>
<div class="share-buttons">
<button class="share-button" id="shareButton">
<i class="fas fa-share-alt"></i>
<span>Share</span>
</button>
<button class="share-button" id="saveButton">
<i class="fas fa-save"></i>
<span>Save</span>
</button>
</div>
</div>
</main>
<footer>
<p>This benchmark tests your device's WebGL 1.0 capabilities. Results may vary between browsers.</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Elements
const runButton = document.getElementById('runBenchmark');
const buttonText = document.getElementById('buttonText');
const spinner = document.getElementById('spinner');
const resultContainer = document.getElementById('resultContainer');
const finalScore = document.getElementById('finalScore');
// Test result elements
const testResults = {
webgl: document.getElementById('webgl-result'),
shader: document.getElementById('shader-result'),
texture: document.getElementById('texture-result'),
geometry: document.getElementById('geometry-result'),
compute: document.getElementById('compute-result')
};
// Progress bars
const progressBars = {
webgl: document.getElementById('webgl-progress'),
shader: document.getElementById('shader-progress'),
texture: document.getElementById('texture-progress'),
geometry: document.getElementById('geometry-progress'),
compute: document.getElementById('compute-progress')
};
// Device info
const performanceCategory = document.getElementById('performance-category');
const rendererInfo = document.getElementById('renderer-info');
const resolutionInfo = document.getElementById('resolution-info');
const fpsInfo = document.getElementById('fps-info');
// Background animation
const backgroundCanvas = document.getElementById('backgroundCanvas');
let gl;
// Set canvas size
function resizeCanvas() {
backgroundCanvas.width = window.innerWidth;
backgroundCanvas.height = window.innerHeight;
if (gl) {
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
}
// Initialize WebGL
function initWebGL() {
try {
gl = backgroundCanvas.getContext('webgl') || backgroundCanvas.getContext('experimental-webgl');
if (!gl) {
return false;
}
// Simple vertex shader
const vsSource = `
attribute vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
// Fragment shader for background animation
const fsSource = `
precision highp float;
uniform float uTime;
uniform vec2 uResolution;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
uv.x *= uResolution.x / uResolution.y;
float time = uTime * 0.3;
// Animated gradient with noise
vec3 col = vec3(
sin(uv.x * 2.0 + time) * 0.5 + 0.5,
cos(uv.y * 3.0 - time * 1.5) * 0.5 + 0.5,
sin((uv.x + uv.y) * 5.0 + time * 2.0) * 0.5 + 0.5
);
gl_FragColor = vec4(col * 0.3, 1.0);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsSource);
gl.compileShader(fragmentShader);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
// Create position buffer
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// Bind position attribute
const aPosition = gl.getAttribLocation(shaderProgram, 'aPosition');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
// Get uniform locations
const uTime = gl.getUniformLocation(shaderProgram, 'uTime');
const uResolution = gl.getUniformLocation(shaderProgram, 'uResolution');
// Animation loop
let startTime = Date.now();
function animate() {
const time = (Date.now() - startTime) / 1000;
gl.uniform1f(uTime, time);
gl.uniform2f(uResolution, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(animate);
}
animate();
return true;
} catch (e) {
console.error('WebGL initialization failed:', e);
return false;
}
}
// Initialize on load
window.addEventListener('load', function() {
resizeCanvas();
// Initial WebGL test
const webglSupported = initWebGL();
testResults.webgl.textContent = webglSupported ? 'Supported' : 'Not Supported';
progressBars.webgl.style.width = webglSupported ? '100%' : '0%';
if (!webglSupported) {
runButton.disabled = true;
buttonText.textContent = 'WebGL Not Supported';
}
// Set device info
if (webglSupported && gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
rendererInfo.textContent = renderer.split('/')[0]; // Clean up long strings
}
}
resolutionInfo.textContent = `${window.innerWidth} × ${window.innerHeight}`;
});
window.addEventListener('resize', function() {
resizeCanvas();
resolutionInfo.textContent = `${window.innerWidth} × ${window.innerHeight}`;
});
// Benchmark tests
async function runBenchmark() {
runButton.disabled = true;
buttonText.textContent = 'Running...';
spinner.style.display = 'block';
// Reset progress bars and results
for (const key in progressBars) {
progressBars[key].style.width = '0%';
testResults[key].textContent = '-';
}
// Wait for initial rendering
await new Promise(resolve => setTimeout(resolve, 300));
try {
let totalScore = 0;
if (!gl) {
throw new Error('WebGL not available');
}
// 1. WebGL Support (already checked)
animateProgress(progressBars.webgl, 100, 200);
testResults.webgl.textContent = 'Supported';
const webglScore = 100;
totalScore += webglScore;
await new Promise(resolve => setTimeout(resolve, 500));
// 2. Shader Complexity Test
const shaderScore = await runShaderTest();
animateProgress(progressBars.shader, shaderScore, 500);
testResults.shader.textContent = `${shaderScore} FPS`;
totalScore += shaderScore;
await new Promise(resolve => setTimeout(resolve, 500));
// 3. Texture Handling Test
const textureScore = await runTextureTest();
animateProgress(progressBars.texture, textureScore, 500);
testResults.texture.textContent = `${textureScore} MB/s`;
totalScore += textureScore;
await new Promise(resolve => setTimeout(resolve, 500));
// 4. Geometry Processing Test
const geometryScore = await runGeometryTest();
animateProgress(progressBars.geometry, geometryScore, 500);
testResults.geometry.textContent = `${geometryScore} K Tris`;
totalScore += geometryScore;
await new Promise(resolve => setTimeout(resolve, 500));
// 5. Compute Performance Test
const computeScore = await runComputeTest();
animateProgress(progressBars.compute, computeScore, 500);
testResults.compute.textContent = `${computeScore} Ops/s`;
totalScore += computeScore;
await new Promise(resolve => setTimeout(resolve, 500));
// Calculate final score (normalized)
const normalizedScore = Math.round(totalScore / 4);
finalScore.textContent = normalizedScore;
fpsInfo.textContent = `${shaderScore} FPS`;
// Set performance category
if (normalizedScore >= 80) {
performanceCategory.textContent = 'Excellent';
} else if (normalizedScore >= 60) {
performanceCategory.textContent = 'Good';
} else if (normalizedScore >= 40) {
performanceCategory.textContent = 'Average';
} else if (normalizedScore >= 20) {
performanceCategory.textContent = 'Below Average';
} else {
performanceCategory.textContent = 'Poor';
}
// Show results
resultContainer.classList.add('show');
} catch (error) {
console.error('Benchmark failed:', error);
alert('Benchmark failed: ' + error.message);
} finally {
runButton.disabled = false;
buttonText.textContent = 'Run Again';
spinner.style.display = 'none';
}
}
function animateProgress(progressBar, targetPercent, duration) {
const startTime = Date.now();
const interval = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const currentWidth = progress * targetPercent;
progressBar.style.width = `${currentWidth}%`;
if (progress === 1) {
clearInterval(interval);
}
}, 16);
}
async function runShaderTest() {
return new Promise(resolve => {
// Measure FPS with a complex shader
const testCanvas = document.createElement('canvas');
testCanvas.width = 512;
testCanvas.height = 512;
const testGl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
if (!testGl) {
resolve(0);
return;
}
// Complex fragment shader for testing
const fsSource = `
precision highp float;
uniform float uTime;
uniform vec2 uResolution;
float noise(vec3 p) {
vec3 i = floor(p);
vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.);
vec3 f = cos((p-i)*acos(-1.))*(-.5)+.5;
a = mix(sin(cos(a)*a),sin(cos(1.+a)*(1.+a)), f.x);
a.xy = mix(a.xz, a.yw, f.y);
return mix(a.x, a.y, f.z);
}
float sphere(vec3 p, vec4 spr) {
return length(spr.xyz-p) - spr.w;
}
float scene(vec3 p) {
float d = sphere(p, vec4(0.0, 0.0, 0.0, 1.0));
for (int i = 0; i < 5; i++) {
float fi = float(i);
d = min(d, sphere(p, vec4(
sin(uTime+fi*2.0)*2.0,
cos(uTime+fi*1.5)*2.0,
sin(uTime+fi*0.9)*2.0,
0.7
)));
}
return d;
}
vec3 getNormal(vec3 p) {
vec3 eps = vec3(0.001, 0.0, 0.0);
return normalize(vec3(
scene(p+eps.xyy) - scene(p-eps.xyy),
scene(p+eps.yxy) - scene(p-eps.yxy),
scene(p+eps.yyx) - scene(p-eps.yyx)
));
}
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
uv = uv * 2.0 - 1.0;
uv.x *= uResolution.x / uResolution.y;
vec3 rd = normalize(vec3(uv, 1.0));
vec3 ro = vec3(0.0, 0.0, -3.0);
vec3 cp = vec3(0.0, 0.0, -2.0);
float t = 0.0;
for (int i = 0; i < 32; i++) {
vec3 p = ro + rd * t;
float d = scene(p);
t += d;
if (d < 0.001 || t > 20.0) break;
}
vec3 p = ro + rd * t;
vec3 n = getNormal(p);
vec3 l = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(0.0, dot(n, l));
vec3 col = vec3(diff) + vec3(0.1);
gl_FragColor = vec4(col, 1.0);
}
`;
const vsSource = `
attribute vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
const vertexShader = testGl.createShader(testGl.VERTEX_SHADER);
testGl.shaderSource(vertexShader, vsSource);
testGl.compileShader(vertexShader);
const fragmentShader = testGl.createShader(testGl.FRAGMENT_SHADER);
testGl.shaderSource(fragmentShader, fsSource);
testGl.compileShader(fragmentShader);
const shaderProgram = testGl.createProgram();
testGl.attachShader(shaderProgram, vertexShader);
testGl.attachShader(shaderProgram, fragmentShader);
testGl.linkProgram(shaderProgram);
testGl.useProgram(shaderProgram);
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
const positionBuffer = testGl.createBuffer();
testGl.bindBuffer(testGl.ARRAY_BUFFER, positionBuffer);
testGl.bufferData(testGl.ARRAY_BUFFER, new Float32Array(positions), testGl.STATIC_DRAW);
const aPosition = testGl.getAttribLocation(shaderProgram, 'aPosition');
testGl.enableVertexAttribArray(aPosition);
testGl.vertexAttribPointer(aPosition, 2, testGl.FLOAT, false, 0, 0);
const uTime = testGl.getUniformLocation(shaderProgram, 'uTime');
const uResolution = testGl.getUniformLocation(shaderProgram, 'uResolution');
// Measure FPS for 2 seconds
let frameCount = 0;
let fps = 0;
let startTime = performance.now();
let lastTime = startTime;
function render() {
const now = performance.now();
frameCount++;
if (now - startTime >= 2000) {
fps = Math.round(frameCount / ((now - startTime) / 1000));
resolve(fps);
return;
}
testGl.uniform1f(uTime, (now - startTime) / 1000);
testGl.uniform2f(uResolution, testCanvas.width, testCanvas.height);
testGl.clear(testGl.COLOR_BUFFER_BIT);
testGl.drawArrays(testGl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(render);
}
render();
});
}
async function runTextureTest() {
return new Promise(resolve => {
const testCanvas = document.createElement('canvas');
testCanvas.width = 512;
testCanvas.height = 512;
const testGl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
if (!testGl) {
resolve(0);
return;
}
// Create texture (512x512 RGBA)
const texture = testGl.createTexture();
testGl.bindTexture(testGl.TEXTURE_2D, texture);
const texSize = 512;
const texData = new Uint8Array(texSize * texSize * 4);
for (let i = 0; i < texSize * texSize * 4; i += 4) {
texData[i] = Math.random() * 255; // R
texData[i+1] = Math.random() * 255; // G
texData[i+2] = Math.random() * 255; // B
texData[i+3] = 255; // A
}
// Upload texture
testGl.texImage2D(testGl.TEXTURE_2D, 0, testGl.RGBA, texSize, texSize, 0, testGl.RGBA, testGl.UNSIGNED_BYTE, texData);
testGl.texParameteri(testGl.TEXTURE_2D, testGl.TEXTURE_MIN_FILTER, testGl.NEAREST);
testGl.texParameteri(testGl.TEXTURE_2D, testGl.TEXTURE_MAG_FILTER, testGl.NEAREST);
// Create simple shader
const vsSource = `
attribute vec2 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord;
}
`;
const fsSource = `
precision highp float;
varying vec2 vTexCoord;
uniform sampler2D uTexture;
void main() {
gl_FragColor = texture2D(uTexture, vTexCoord);
}
`;
const vertexShader = testGl.createShader(testGl.VERTEX_SHADER);
testGl.shaderSource(vertexShader, vsSource);
testGl.compileShader(vertexShader);
const fragmentShader = testGl.createShader(testGl.FRAGMENT_SHADER);
testGl.shaderSource(fragmentShader, fsSource);
testGl.compileShader(fragmentShader);
const shaderProgram = testGl.createProgram();
testGl.attachShader(shaderProgram, vertexShader);
testGl.attachShader(shaderProgram, fragmentShader);
testGl.linkProgram(shaderProgram);
testGl.useProgram(shaderProgram);
// Fullscreen quad with tex coords
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
const texCoords = [0, 0, 1, 0, 0, 1, 1, 1];
const positionBuffer = testGl.createBuffer();
testGl.bindBuffer(testGl.ARRAY_BUFFER, positionBuffer);
testGl.bufferData(testGl.ARRAY_BUFFER, new Float32Array(positions), testGl.STATIC_DRAW);
const texCoordBuffer = testGl.createBuffer();
testGl.bindBuffer(testGl.ARRAY_BUFFER, texCoordBuffer);
testGl.bufferData(testGl.ARRAY_BUFFER, new Float32Array(texCoords), testGl.STATIC_DRAW);
const aPosition = testGl.getAttribLocation(shaderProgram, 'aPosition');
testGl.enableVertexAttribArray(aPosition);
testGl.bindBuffer(testGl.ARRAY_BUFFER, positionBuffer);
testGl.vertexAttribPointer(aPosition, 2, testGl.FLOAT, false, 0, 0);
const aTexCoord = testGl.getAttribLocation(shaderProgram, 'aTexCoord');
testGl.enableVertexAttribArray(aTexCoord);
testGl.bindBuffer(testGl.ARRAY_BUFFER, texCoordBuffer);
testGl.vertexAttribPointer(aTexCoord, 2, testGl.FLOAT, false, 0, 0);
const uTexture = testGl.getUniformLocation(shaderProgram, 'uTexture');
testGl.uniform1i(uTexture, 0);
// Measure texture rendering speed
let frameCount = 0;
let startTime = performance.now();
let mbPerSecond = 0;
function render() {
frameCount++;
if (frameCount === 100) {
const duration = (performance.now() - startTime) / 1000; // in seconds
const totalData = 512 * 512 * 4 * frameCount; // bytes
mbPerSecond = Math.round((totalData / (1024 * 1024)) / duration);
resolve(mbPerSecond);
return;
}
// Modify texture to force upload
if (frameCount % 10 === 0) {
for (let i = 0; i < texSize * texSize * 4; i += 4) {
texData[i] = Math.random() * 255;
texData[i+1] = Math.random() * 255;
texData[i+2] = Math.random() * 255;
}
testGl.texSubImage2D(testGl.TEXTURE_2D, 0, 0, 0, texSize, texSize, testGl.RGBA, testGl.UNSIGNED_BYTE, texData);
}
testGl.clear(testGl.COLOR_BUFFER_BIT);
testGl.drawArrays(testGl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(render);
}
render();
});
}
async function runGeometryTest() {
return new Promise(resolve => {
const testCanvas = document.createElement('canvas');
testCanvas.width = 512;
testCanvas.height = 512;
const testGl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
if (!testGl) {
resolve(0);
return;
}
// Create simple shader
const vsSource = `
attribute vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
gl_PointSize = 2.0;
}
`;
const fsSource = `
precision highp float;
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`;
const vertexShader = testGl.createShader(testGl.VERTEX_SHADER);
testGl.shaderSource(vertexShader, vsSource);
testGl.compileShader(vertexShader);
const fragmentShader = testGl.createShader(testGl.FRAGMENT_SHADER);
testGl.shaderSource(fragmentShader, fsSource);
testGl.compileShader(fragmentShader);
const shaderProgram = testGl.createProgram();
testGl.attachShader(shaderProgram, vertexShader);
testGl.attachShader(shaderProgram, fragmentShader);
testGl.linkProgram(shaderProgram);
testGl.useProgram(shaderProgram);
// Create position buffer (will be updated each frame)
const positionBuffer = testGl.createBuffer();
testGl.bindBuffer(testGl.ARRAY_BUFFER, positionBuffer);
const aPosition = testGl.getAttribLocation(shaderProgram, 'aPosition');
testGl.enableVertexAttribArray(aPosition);
testGl.vertexAttribPointer(aPosition, 2, testGl.FLOAT, false, 0, 0);
// Measure geometry processing speed
let maxTriangles = 0;
let currentCount = 1000; // start with 1k triangles
let startTime = performance.now();
let lastStableCount = 0;
let stableFrames = 0;
function render() {
// Generate triangles (each triangle has 3 points)
const positions = new Float32Array(currentCount * 3 * 2);
for (let i = 0; i < currentCount * 3 * 2; i += 2) {
positions[i] = Math.random() * 2 - 1;
positions[i+1] = Math.random() * 2 - 1;
}
testGl.bufferData(testGl.ARRAY_BUFFER, positions, testGl.DYNAMIC_DRAW);
testGl.clear(testGl.COLOR_BUFFER_BIT);
testGl.drawArrays(testGl.TRIANGLES, 0, currentCount * 3);
// Check if we can increase the count
const frameTime = performance.now() - startTime;
if (frameTime < 16) { // ~60fps
currentCount *= 1.2;
stableFrames = 0;
} else if (frameTime < 33) { // ~30fps
stableFrames++;
if (stableFrames > 5) {
lastStableCount = Math.round(currentCount);
resolve(Math.round(lastStableCount / 1000)); // return in thousands
return;
}
currentCount *= 1.05;
} else { // below 30fps
lastStableCount = Math.round(currentCount * 0.8);
resolve(Math.round(lastStableCount / 1000)); // return in thousands
return;
}
startTime = performance.now();
requestAnimationFrame(render);
}
render();
});
}
async function runComputeTest() {
return new Promise(resolve => {
// This test uses WebGL to simulate general computation (like GPGPU)
const testCanvas = document.createElement('canvas');
testCanvas.width = 256;
testCanvas.height = 256;
const testGl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
if (!testGl) {
resolve(0);
return;
}
// Create texture for computation
const texture = testGl.createTexture();
testGl.bindTexture(testGl.TEXTURE_2D, texture);
testGl.texParameteri(testGl.TEXTURE_2D, testGl.TEXTURE_MIN_FILTER, testGl.NEAREST);
testGl.texParameteri(testGl.TEXTURE_2D, testGl.TEXTURE_MAG_FILTER, testGl.NEAREST);
testGl.texParameteri(testGl.TEXTURE_2D, testGl.TEXTURE_WRAP_S, testGl.CLAMP_TO_EDGE);
testGl.texParameteri(testGl.TEXTURE_2D, testGl.TEXTURE_WRAP_T, testGl.CLAMP_TO_EDGE);
const texSize = 256;
testGl.texImage2D(testGl.TEXTURE_2D, 0, testGl.RGBA, texSize, texSize, 0, testGl.RGBA, testGl.UNSIGNED_BYTE, null);
// Create framebuffer
const framebuffer = testGl.createFramebuffer();
testGl.bindFramebuffer(testGl.FRAMEBUFFER, framebuffer);
testGl.framebufferTexture2D(testGl.FRAMEBUFFER, testGl.COLOR_ATTACHMENT0, testGl.TEXTURE_2D, texture, 0);
// Compute shader (simulates many operations)
const fsSource = `
precision highp float;
uniform vec2 uResolution;
uniform float uTime;
uniform int uIterations;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
// Complex computation simulation
vec2 p = uv * 2.0 - 1.0;
float v = 0.0;
for (int i = 0; i < 1024; i++) {
if (i >= uIterations) break;
float fi = float(i);
v += 0.01 * sin(
p.x * sin(uTime * 0.2 + fi * 0.1) * 50.0 +
p.y * sin(uTime * 0.3 + fi * 0.2) * 50.0 +
uTime * 2.0
);
p = vec2(
sin(v) + p.x + cos(uTime + fi * 0.3),
cos(v) + p.y + sin(uTime + fi * 0.4)
);
}
gl_FragColor = vec4(vec3(v), 1.0);
}
`;
const vsSource = `
attribute vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
const vertexShader = testGl.createShader(testGl.VERTEX_SHADER);
testGl.shaderSource(vertexShader, vsSource);
testGl.compileShader(vertexShader);
const fragmentShader = testGl.createShader(testGl.FRAGMENT_SHADER);
testGl.shaderSource(fragmentShader, fsSource);
testGl.compileShader(fragmentShader);
const shaderProgram = testGl.createProgram();
testGl.attachShader(shaderProgram, vertexShader);
testGl.attachShader(shaderProgram, fragmentShader);
testGl.linkProgram(shaderProgram);
testGl.useProgram(shaderProgram);
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
const positionBuffer = testGl.createBuffer();
testGl.bindBuffer(testGl.ARRAY_BUFFER, positionBuffer);
testGl.bufferData(testGl.ARRAY_BUFFER, new Float32Array(positions), testGl.STATIC_DRAW);
const aPosition = testGl.getAttribLocation(shaderProgram, 'aPosition');
testGl.enableVertexAttribArray(aPosition);
testGl.vertexAttribPointer(aPosition, 2, testGl.FLOAT, false, 0, 0);
const uResolution = testGl.getUniformLocation(shaderProgram, 'uResolution');
const uTime = testGl.getUniformLocation(shaderProgram, 'uTime');
const uIterations = testGl.getUniformLocation(shaderProgram, 'uIterations');
// Find maximum iterations that can run at 60fps
let iterations = 10;
let lastStable = 0;
let startTime = performance.now();
let opsPerSecond = 0;
function render() {
testGl.uniform2f(uResolution, texSize, texSize);
testGl.uniform1f(uTime, (performance.now() - startTime) / 1000);
testGl.uniform1i(uIterations, iterations);
testGl.viewport(0, 0, texSize, texSize);
testGl.clear(testGl.COLOR_BUFFER_BIT);
testGl.drawArrays(testGl.TRIANGLE_STRIP, 0, 4);
// Read back result to ensure computation happens
const pixels = new Uint8Array(4);
testGl.readPixels(0, 0, 1, 1, testGl.RGBA, testGl.UNSIGNED_BYTE, pixels);
// Measure frame time
const frameTime = performance.now() - startTime;
if (frameTime < 16) { // ~60fps
iterations = Math.floor(iterations * 1.3);
} else {
// Estimate ops per second
opsPerSecond = Math.round(iterations * texSize * texSize * 60);
resolve(opsPerSecond / 1000000); // return in millions
return;
}
startTime = performance.now();
requestAnimationFrame(render);
}
render();
});
}
// Event listeners
runButton.addEventListener('click', runBenchmark);
// Share button
document.getElementById('shareButton').addEventListener('click', function() {
if (navigator.share) {
navigator.share({
title: 'My GPU Benchmark Results',
text: `I scored ${finalScore.textContent} on the Mobile GPU Benchmark!`,
url: window.location.href
}).catch(err => {
console.log('Error sharing:', err);
fallbackShare();
});
} else {
fallbackShare();
}
});
function fallbackShare() {
const text = `My GPU Benchmark Score: ${finalScore.textContent}\n\nTest your device at: ${window.location.href}`;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
alert('Results copied to clipboard!');
}).catch(err => {
prompt('Copy to clipboard:', text);
});
} else {
prompt('Copy to clipboard:', text);
}
}
// Save button
document.getElementById('saveButton').addEventListener('click', function() {
alert('In a real app, this would save your results to local storage or export them.');
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: absolute; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">This website has been generated by <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>