// @ts-nocheck // based on: https://github.com/munrocket/gl-bench const UICSS = ` #gl-bench { position: absolute; right: 1rem; bottom: 1rem; z-index:1000; -webkit-user-select: none; -moz-user-select: none; user-select: none; } #gl-bench div { position: relative; display: block; margin: 4px; padding: 0 2px 0 2px; background: #303030; border-radius: 0.1rem; cursor: pointer; opacity: 0.9; } #gl-bench svg { height: 60px; margin: 0 0px 0px 4px; } #gl-bench text { font-size: 16px; font-family: 'Lato', 'Segoe UI'; dominant-baseline: middle; text-anchor: middle; } #gl-bench .gl-mem { font-size: 12px; fill: white; } #gl-bench .gl-fps { font-size: 13px; fill: white; } #gl-bench line { stroke-width: 5; stroke: white; stroke-linecap: round; } #gl-bench polyline { fill: none; stroke: white; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3.5; } #gl-bench rect { fill: black; } #gl-bench .opacity { stroke: black; } `; const UISVG = `
00 FPS
`; class GLBench { /** GLBench constructor * @param { WebGLRenderingContext | WebGL2RenderingContext } gl context * @param { Object | undefined } settings additional settings */ constructor(gl, settings = {}) { this.css = UICSS; this.svg = UISVG; // eslint-disable-next-line @typescript-eslint/no-empty-function this.paramLogger = () => {}; // eslint-disable-next-line @typescript-eslint/no-empty-function this.chartLogger = () => {}; this.chartLen = 20; this.chartHz = 20; this.names = []; this.cpuAccums = []; this.gpuAccums = []; this.activeAccums = []; this.chart = new Array(this.chartLen); this.now = () => ((performance && performance.now) ? performance.now() : Date.now()); this.updateUI = () => { [].forEach.call(this.nodes['gl-gpu-svg'], (node) => node.style.display = this.trackGPU ? 'inline' : 'none'); }; Object.assign(this, settings); this.detected = 0; this.finished = []; this.isFramebuffer = 0; this.frameId = 0; // 120hz device detection let rafId; let n = 0; let t0; const loop = (t) => { if (++n < 20) { rafId = requestAnimationFrame(loop); } else { this.detected = Math.ceil(1e3 * n / (t - t0) / 70); cancelAnimationFrame(rafId); } if (!t0) t0 = t; }; requestAnimationFrame(loop); // attach gpu profilers if (gl) { const glFinish = async (t, activeAccums) => Promise.resolve(setTimeout(() => { gl.getError(); const dt = this.now() - t; activeAccums.forEach((active, i) => { if (active) this.gpuAccums[i] += dt; }); }, 0)); const addProfiler = (fn, self, target) => { const t = self.now(); // eslint-disable-next-line prefer-rest-params fn.apply(target, arguments); if (self.trackGPU) self.finished.push(glFinish(t, self.activeAccums.slice(0))); }; /* ['drawArrays', 'drawElements', 'drawArraysInstanced', 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'].forEach((fn) => { if (gl[fn]) { gl[fn] = addProfiler(gl[fn], this, gl); } }); */ const fn = 'drawElements'; if (gl[fn]) { gl[fn] = addProfiler(gl[fn], this, gl); } else { // eslint-disable-next-line no-console console.log('bench: cannot attach to webgl function'); } /* gl.getExtension = ((fn, self) => { // eslint-disable-next-line prefer-rest-params const ext = fn.apply(gl, arguments); if (ext) { ['drawElementsInstancedANGLE', 'drawBuffersWEBGL'].forEach((fn2) => { if (ext[fn2]) { ext[fn2] = addProfiler(ext[fn2], self, ext); } }); } return ext; })(gl.getExtension, this); */ } // init ui and ui loggers if (!this.withoutUI) { if (!this.dom) this.dom = document.body; const elm = document.createElement('div'); elm.id = 'gl-bench'; this.dom.appendChild(elm); this.dom.insertAdjacentHTML('afterbegin', ''); this.dom = elm; this.dom.addEventListener('click', () => { this.trackGPU = !this.trackGPU; this.updateUI(); }); this.paramLogger = ((logger, dom, names) => { const classes = ['gl-cpu', 'gl-gpu', 'gl-mem', 'gl-fps', 'gl-gpu-svg', 'gl-chart']; const nodes = { ...classes }; classes.forEach((c) => nodes[c] = dom.getElementsByClassName(c)); this.nodes = nodes; return (i, cpu, gpu, mem, fps, totalTime, frameId) => { nodes['gl-cpu'][i].style.strokeDasharray = (cpu * 0.27).toFixed(0) + ' 100'; nodes['gl-gpu'][i].style.strokeDasharray = (gpu * 0.27).toFixed(0) + ' 100'; // eslint-disable-next-line no-nested-ternary nodes['gl-mem'][i].innerHTML = names[i] ? names[i] : (mem ? 'mem: ' + mem.toFixed(0) + 'mb' : ''); nodes['gl-fps'][i].innerHTML = 'FPS: ' + fps.toFixed(1); logger(names[i], cpu, gpu, mem, fps, totalTime, frameId); }; })(this.paramLogger, this.dom, this.names); this.chartLogger = ((logger, dom) => { const nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') }; return (i, chart, circularId) => { let points = ''; const len = chart.length; for (let j = 0; j < len; j++) { const id = (circularId + j + 1) % len; if (chart[id] !== undefined) points = points + ' ' + (60 * j / (len - 1)).toFixed(1) + ',' + (45 - chart[id] * 0.5 / this.detected).toFixed(1); } nodes['gl-chart'][i].setAttribute('points', points); logger(this.names[i], chart, circularId); }; })(this.chartLogger, this.dom); } } /** * Explicit UI add * @param { string | undefined } name */ addUI(name) { if (this.names.indexOf(name) === -1) { this.names.push(name); if (this.dom) { this.dom.insertAdjacentHTML('beforeend', this.svg); this.updateUI(); } this.cpuAccums.push(0); this.gpuAccums.push(0); this.activeAccums.push(false); } } /** * Increase frameID * @param { number | undefined } now */ nextFrame(now) { this.frameId++; const t = now || this.now(); // params if (this.frameId <= 1) { this.paramFrame = this.frameId; this.paramTime = t; } else { const duration = t - this.paramTime; if (duration >= 1e3) { const frameCount = this.frameId - this.paramFrame; const fps = frameCount / duration * 1e3; for (let i = 0; i < this.names.length; i++) { const cpu = this.cpuAccums[i] / duration * 100; const gpu = this.gpuAccums[i] / duration * 100; const mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0; this.paramLogger(i, cpu, gpu, mem, fps, duration, frameCount); this.cpuAccums[i] = 0; Promise.all(this.finished).then(() => { this.gpuAccums[i] = 0; this.finished = []; }); } this.paramFrame = this.frameId; this.paramTime = t; } } // chart if (!this.detected || !this.chartFrame) { this.chartFrame = this.frameId; this.chartTime = t; this.circularId = 0; } else { const timespan = t - this.chartTime; let hz = this.chartHz * timespan / 1e3; while (--hz > 0 && this.detected) { const frameCount = this.frameId - this.chartFrame; const fps = frameCount / timespan * 1e3; this.chart[this.circularId % this.chartLen] = fps; for (let i = 0; i < this.names.length; i++) this.chartLogger(i, this.chart, this.circularId); this.circularId++; this.chartFrame = this.frameId; this.chartTime = t; } } } /** * Begin named measurement * @param { string | undefined } name */ begin(name) { this.updateAccums(name); } /** * End named measure * @param { string | undefined } name */ end(name) { this.updateAccums(name); } updateAccums(name) { let nameId = this.names.indexOf(name); if (nameId === -1) { nameId = this.names.length; this.addUI(name); } const t = this.now(); const dt = t - this.t0; for (let i = 0; i < nameId + 1; i++) { if (this.activeAccums[i]) this.cpuAccums[i] += dt; } this.activeAccums[nameId] = !this.activeAccums[nameId]; this.t0 = t; } } export default GLBench;