import { pack } from './pack.mjs'; import WebGLScene from '/scene3d-webgl.mjs'; const frames = await (await fetch('/trace.json')).json(); const canvas = document.querySelector('#canvas'), gpuCanvas = document.querySelector('#gpu'); const context = canvas.getContext('2d', { alpha: false }); const max = Math.max(...frames.map(f => f.triangles.length / 12)); const renderer = new WebGLScene(gpuCanvas, { maxTriangles: max }); const gl = renderer.gl; gl.disable(gl.DEPTH_TEST); // This comparison preserves Canvas painter order. const timer = gl.getExtension('EXT_disjoint_timer_query_webgl2'); const debug = gl.getExtension('WEBGL_debug_renderer_info'); const nextFrame = () => new Promise(requestAnimationFrame); const stats = values => { if (!values.length) return null; const v = values.slice().sort((a,b)=>a-b); return { samples: v.length, p50: v[Math.floor(v.length*.5)], p95: v[Math.min(v.length-1,Math.floor(v.length*.95))], p99: v[Math.min(v.length-1,Math.floor(v.length*.99))] }; }; const retained = frames.map(frame => { const v = new Float32Array(frame.triangles.length / 12 * 18); pack(frame,v); return v; }); function drawCanvas(frame, width, height) { context.setTransform(width/1920,0,0,height/1080,0,0); context.fillStyle = `rgb(${frame.clear.join(',')})`; context.fillRect(0,0,1920,1080); let ink = '', pending = false; const t = frame.triangles; for (let i=0;iv/255),1] }); } async function run(mode,width,height,count) { canvas.hidden=mode!=='canvas';gpuCanvas.hidden=mode==='canvas'; canvas.width=width;canvas.height=height;renderer.resize(width,height); const cpu=[], intervals=[], completed=[], gpu=[], queries=[]; const draw=i=>mode==='canvas'?drawCanvas(frames[i],width,height):drawGpu(frames[i],i,mode); for(let i=0;i<30;i++){await nextFrame();draw(i%frames.length);} let previous=await nextFrame(), disjoint=false; for(let i=0;i=0;i--) if(gl.getQueryParameter(queries[i],gl.QUERY_RESULT_AVAILABLE)) { gpu.push(gl.getQueryParameter(queries[i],gl.QUERY_RESULT)/1e6);gl.deleteQuery(queries[i]);queries.splice(i,1); } } const pendingQueries=queries.length;for(const q of queries)gl.deleteQuery(q); if (gl.isContextLost()) throw new Error('WebGL context lost during benchmark'); const error = gl.getError(); if (error !== gl.NO_ERROR) throw new Error('WebGL error: ' + error); // Same frame for the comparison screenshots; no simulation runs here. draw(20); return {mode,width,height,cpuMs:stats(cpu),rafIntervalMs:stats(intervals), callbacksOver25ms:intervals.filter(v=>v>25).length,completionProbeMs:stats(completed), gpuMs:disjoint?null:stats(gpu),gpuDisjoint:disjoint,pendingQueries, completionProbe:mode==='canvas'?'1px getImageData forces readback; includes readback overhead':'gl.finish forces GPU completion; excludes display/compositor'}; } async function idle(count) { canvas.hidden = false; gpuCanvas.hidden = true; const intervals = []; let previous = await nextFrame(); for (let i = 0; i < count; i++) { const now = await nextFrame(); intervals.push(now - previous); previous = now; } return { rafIntervalMs: stats(intervals), callbacksOver25ms: intervals.filter(v => v > 25).length }; } globalThis.bench={run,idle,hardware:()=>({userAgent:navigator.userAgent, webglVersion:gl.getParameter(gl.VERSION),renderer:debug?gl.getParameter(debug.UNMASKED_RENDERER_WEBGL):gl.getParameter(gl.RENDERER), gpuTimerAvailable:!!timer,antialias:gl.getContextAttributes().antialias,devicePixelRatio, visibility:document.visibilityState})};